บทความนี้อธิบายวิธีการประมวลผลแบทช์ไฮเบอร์เนตของข้อมูลขนาดใหญ่ แบ่งปันสำหรับการอ้างอิงของคุณดังนี้:
การประมวลผลแบทช์แบตช์ไฮเบอร์เนตนั้นไม่เป็นที่พึงปรารถนาจากมุมมองของประสิทธิภาพและสูญเปล่าหน่วยความจำจำนวนมาก จากกลไกของมัน Hibernate ก่อนตรวจสอบข้อมูลที่ตรงกับเงื่อนไขวางไว้ในหน่วยความจำแล้วดำเนินการ ประสิทธิภาพไม่น่าพอใจมากในการใช้งานจริง ในการใช้งานจริงของฉันข้อมูลของโซลูชันการเพิ่มประสิทธิภาพที่สามต่อไปนี้คือ: ข้อมูล 100,000 ชิ้นถูกแทรกลงในฐานข้อมูลซึ่งใช้เวลาประมาณ 30 นาที ฮ่าฮ่าจาง ๆ (ฉันใส่ข้อมูล 1,000,000 ชิ้นใน 10 นาที (ฟิลด์ค่อนข้างเล็ก))
มีสามวิธีในการจัดการกับมันเพื่อแก้ปัญหาประสิทธิภาพ:
1: บายพาส Hibernate API และใช้ JDBC API โดยตรง วิธีนี้มีประสิทธิภาพที่ดีขึ้น นอกจากนี้ยังเร็วที่สุด
2: ใช้ขั้นตอนที่เก็บไว้
3: ใช้ Hibernate API เพื่อดำเนินการประมวลผลแบทช์ปกติ อาจมีการเปลี่ยนแปลงและการเปลี่ยนแปลงจะเปลี่ยนแปลง เมื่อเราพบจำนวนเงินที่กำหนดเราสามารถลบข้อมูลได้ทันเวลาหลังจากดำเนินการเสร็จสิ้นเซสชัน flush (); session.evict (ชุดวัตถุ xx); นอกจากนี้ยังสามารถบันทึกการสูญเสียประสิทธิภาพบางอย่าง "จำนวนที่แน่นอน" นี้จะต้องใช้เป็นข้อมูลอ้างอิงเชิงปริมาณตามเงื่อนไขจริง โดยทั่วไปประมาณ 30-60 แต่เอฟเฟกต์ยังไม่เหมาะ
1: บายพาส Hibernate API และทำโดยตรงผ่าน JDBC API วิธีนี้มีประสิทธิภาพที่ดีขึ้นและเร็วที่สุด (ตัวอย่างคือการดำเนินการอัปเดต)
ธุรกรรม tx = session.beginTransaction (); // โปรดทราบว่าคุณกำลังใช้การเชื่อมต่อขอบเขตธุรกรรม Hibernate Connection Conn = session.connection (); PreparedStatement STMT = Conn.PreparedStatement ("อัปเดตลูกค้าเป็น C SET C.SARLARY = C.SARLARY+1 โดยที่ C.SARLARY> 1,000"); stmt.excuteUpdate (); tx.commit (); // โปรดทราบว่าคุณกำลังใช้ขอบเขตการทำธุรกรรมไฮเบอร์เนต ในแอปเพล็ตนี้จะใช้ API ที่เรียก JDBC โดยตรงเพื่อเข้าถึงฐานข้อมูลซึ่งมีประสิทธิภาพมาก หลีกเลี่ยงปัญหาด้านประสิทธิภาพที่เกิดจากการสอบถามครั้งแรกและการโหลดลงในหน่วยความจำแล้วดำเนินการ
2: ใช้ขั้นตอนที่เก็บไว้ อย่างไรก็ตามวิธีนี้ไม่แนะนำให้ใช้เนื่องจากความสะดวกสบายของการพกพาและการปรับใช้โปรแกรม (ตัวอย่างคือการดำเนินการอัปเดต)
หากฐานข้อมูลพื้นฐาน (เช่น Oracle) รองรับขั้นตอนที่เก็บไว้การอัปเดตแบบแบตช์สามารถทำได้ผ่านขั้นตอนที่เก็บไว้ ขั้นตอนที่เก็บไว้ทำงานโดยตรงในฐานข้อมูลเร็วขึ้น ในฐานข้อมูล Oracle สามารถกำหนดขั้นตอนที่เก็บไว้ชื่อ BatchUpDateCustomer () ได้รหัสมีดังนี้:
การคัดลอกรหัสมีดังนี้: สร้างหรือแทนที่โพรซีเดอร์ batchUpDateCustomer (p_age จำนวน) เป็นเริ่มต้นอัปเดตลูกค้าตั้งค่าอายุ = อายุ+1 โดยที่อายุ> p_age; สิ้นสุด;
ขั้นตอนที่เก็บไว้ข้างต้นมีพารามิเตอร์ p_age ซึ่งแสดงถึงอายุของไคลเอนต์ แอปพลิเคชันสามารถเรียกขั้นตอนที่เก็บไว้ด้วยวิธีต่อไปนี้:
tx = session.beginTransaction (); การเชื่อมต่อ con = session.connection (); โพรซีเดอร์สตริง = "{เรียก batchupdateCustomer (?)}"; callablestatement cstmt = con.preparecall (ขั้นตอน); cstmt.setint (1, 0); // ตั้งค่าพารามิเตอร์อายุเป็น 0cstmt.executeUpdate (); tx.commit ();ดังที่เห็นได้จากโปรแกรมข้างต้นแอปพลิเคชันจะต้องข้าม Hibernate API และเรียกขั้นตอนที่เก็บไว้โดยตรงผ่าน JDBC API
3: ใช้ Hibernate API เพื่อดำเนินการประมวลผลแบทช์ปกติ อาจมีการเปลี่ยนแปลงและการเปลี่ยนแปลงจะเปลี่ยนแปลง เมื่อเราพบจำนวนเงินที่กำหนดเราสามารถลบข้อมูลได้ทันเวลาหลังจากดำเนินการเสร็จสิ้นเซสชัน flush (); session.evict (ชุดวัตถุ xx); นอกจากนี้ยังสามารถบันทึกการสูญเสียประสิทธิภาพบางอย่าง "จำนวนที่แน่นอน" นี้จะต้องเป็นข้อมูลอ้างอิงเชิงปริมาณตามเงื่อนไขจริง ...
(ตัวอย่างคือการดำเนินการบันทึก)
ตรรกะทางธุรกิจคือ: เราต้องการแทรกข้อมูล 10 0000 ชิ้นลงในฐานข้อมูล
tx = session.begInTransaction (); สำหรับ (int i = 0; i <100000; i ++) {ลูกค้ากำหนดเอง = ลูกค้าใหม่ (); custom.setName ("ผู้ใช้"+i); เซสชัน SAVE (กำหนดเอง); ถ้า (i%50 == 0) // ใช้ทุก 50 ข้อมูลสิ่งนี้จะทำให้ระบบอยู่ในช่วงที่มั่นคง ...
ในระหว่างกระบวนการพัฒนาโครงการเนื่องจากข้อกำหนดของโครงการเรามักจะต้องแทรกข้อมูลจำนวนมากลงในฐานข้อมูล มีหมื่นหมื่นหมื่นคนหลายหมื่นล้านคนแม้กระทั่งหลายหมื่นล้านคน หากคุณใช้ไฮเบอร์เนตเพื่อแทรกข้อมูลระดับของระดับนี้อาจมีข้อยกเว้นเกิดขึ้น ข้อยกเว้นทั่วไปคือ OutofMemoryError (Memory Overflow Exception)
ก่อนอื่นมาทบทวนกลไกการทำงานของการแทรกไฮเบอร์เนตสั้น ๆ ไฮเบอร์เนตจำเป็นต้องรักษาแคชภายใน เมื่อเราดำเนินการแทรกเราจะทำให้วัตถุทั้งหมดทำงานในแคชภายในของเราสำหรับการจัดการ
เมื่อพูดถึงแคชของไฮเบอร์เนตไฮเบอร์เนตมีทฤษฎีแคชภายในและแคชทุติยภูมิ เนื่องจาก Hibernate มีกลไกการจัดการที่แตกต่างกันสำหรับแคชทั้งสองนี้เราจึงสามารถกำหนดขนาดของมันที่เกี่ยวข้องกับแคชรองในขณะที่สำหรับแคชภายใน Hibernate จึงใช้ทัศนคติ "สายจูง" และไม่มีการ จำกัด ความสามารถของมัน ตอนนี้พบปัญหาของปัญหา เมื่อเราแทรกข้อมูลจำนวนมากวัตถุจำนวนมากจะรวมอยู่ในแคชภายใน (แคชภายในถูกแคชในหน่วยความจำ) เพื่อให้หน่วยความจำระบบของคุณจะถูกกินทีละบิต หากระบบเป็น "ทอด" ในที่สุดก็สมเหตุสมผล
ลองคิดเกี่ยวกับวิธีจัดการกับปัญหานี้ดีกว่า เงื่อนไขการพัฒนาบางอย่างจะต้องได้รับการจัดการโดยใช้ไฮเบอร์เนตและแน่นอนว่าบางโครงการมีความยืดหยุ่นมากขึ้นและคุณสามารถหาวิธีอื่นได้
ที่นี่ฉันแนะนำสองวิธี:
(1): เพิ่มประสิทธิภาพไฮเบอร์เนตและใช้วิธีการแทรกแบบแบ่งส่วนเพื่อล้างแคชในเวลาในโปรแกรม
(2): บายพาส Hibernate API และทำการแทรกแบทช์โดยตรงผ่าน JDBC API วิธีนี้มีประสิทธิภาพที่ดีที่สุดและเร็วที่สุด
สำหรับวิธีการที่ 1 ด้านบนแนวคิดพื้นฐานคือ: เพิ่มประสิทธิภาพไฮเบอร์เนตให้ตั้งค่าพารามิเตอร์ hibernate.jdbc.batch_size ในไฟล์การกำหนดค่าเพื่อระบุจำนวน SQL ที่ส่งในแต่ละครั้ง โปรแกรมใช้วิธีการล้างแคชในเวลาในการแทรกแบบแบ่งส่วน (เซสชันใช้การเขียนแบบอะซิงโครนัสซึ่งช่วยให้ไฮเบอร์เนตสามารถเขียนการดำเนินการอย่างชัดเจน) นั่นคือล้างพวกเขาจากแคชภายในในเวลาหลังจากแทรกข้อมูลจำนวนหนึ่งและปลดปล่อยหน่วยความจำที่ถูกครอบครอง
ในการตั้งค่าพารามิเตอร์ hibernate.jdbc.batch_size คุณสามารถอ้างถึงการกำหนดค่าต่อไปนี้
<hibernate-configuration> <Session-factory> … <property name = "hibernate.jdbc.batch_size"> 50 </porement>
เหตุผลในการกำหนดค่าพารามิเตอร์ hibernate.jdbc.batch_size คือการอ่านฐานข้อมูลให้น้อยที่สุด ยิ่งค่าพารามิเตอร์ของ hibernate.jdbc.batch_size มากขึ้นเท่าใดเวลาที่คุณอ่านฐานข้อมูลก็จะยิ่งน้อยลงและความเร็วก็เร็วขึ้นเท่านั้น จากการกำหนดค่าข้างต้นจะเห็นได้ว่า Hibernate รอจนกว่าโปรแกรมจะสะสม 50 sql ก่อนที่จะส่งเป็นแบทช์
ผู้เขียนยังคิดว่าค่าของพารามิเตอร์ hibernate.jdbc.batch_size อาจไม่ได้ถูกตั้งค่าให้ใหญ่ที่สุดเท่าที่จะเป็นไปได้และยังคงต้องพูดคุยจากมุมมองของประสิทธิภาพ สิ่งนี้ต้องพิจารณาสถานการณ์จริงและตั้งค่าตามความเหมาะสม โดยทั่วไปการตั้งค่า 30 หรือ 50 สามารถตอบสนองความต้องการ
ในแง่ของการใช้งานโปรแกรมผู้เขียนจะใช้ข้อมูล 10,000 ชิ้นเป็นตัวอย่าง
เซสชั่นเซสชัน = hibernateutil.currentsession (); transatcion tx = session.begintransaction (); สำหรับ (int i = 0; i <10,000; i ++) {นักเรียน st = นักเรียนใหม่ (); stetname ("feifei"); session.save.save; // เก็บซิงโครนัสด้วยฐานข้อมูลเซสชันข้อมูล clear (); // ล้างข้อมูลทั้งหมดที่แคชภายในและปล่อยหน่วยความจำที่ถูกครอบครองในเวลา}} tx.commit (); ...ภายใต้มาตราส่วนข้อมูลบางอย่างวิธีการนี้สามารถรักษาทรัพยากรหน่วยความจำของระบบในช่วงที่ค่อนข้างเสถียร
หมายเหตุ: แคชระดับที่สองที่กล่าวถึงก่อนหน้านี้เป็นสิ่งจำเป็นสำหรับฉันที่จะพูดถึงที่นี่ หากเปิดใช้งานแคชทุติยภูมิเพื่อรักษาแคชทุติยภูมิไฮเบอร์เนตจะเรียกเก็บข้อมูลที่เกี่ยวข้องกับแคชรองเมื่อเราแทรกอัปเดตและลบการดำเนินการ จะมีการสูญเสียอย่างมากในการปฏิบัติงานดังนั้นผู้เขียนแนะนำให้ปิดการใช้งานแคชระดับ 2 ในการประมวลผลแบบแบตช์
สำหรับวิธีการที่ 2 ใช้การประมวลผลแบทช์ JDBC แบบดั้งเดิมและใช้ JDBC API ในการประมวลผล
โปรดดูการประมวลผลแบบแบตช์ Java และ SQL แบบ execution ด้วยตนเอง
เมื่อดูที่รหัสข้างต้นคุณมักจะรู้สึกว่ามีบางอย่างที่ไม่เหมาะสมหรือไม่? ใช่คุณไม่ได้สังเกต! นี่คือการเขียนโปรแกรมแบบดั้งเดิมของ JDBC โดยไม่มีรสชาติที่จำศีล
รหัสข้างต้นสามารถแก้ไขได้ตามนี้:
ธุรกรรม tx = session.beginTransaction (); // ใช้การเชื่อมต่อการประมวลผลการประมวลผลธุรกรรมไฮเบอร์เนต conn = session.connection (); การเตรียมการ stmt = conn.preparestatement ("แทรกลงในค่า t_student (ชื่อ) (?)"); สำหรับ (int j = 0; j ++; j <200) {สำหรับ (int i = 0; i ++; j <50) {stmt.setstring (1, "feifei");}} stmt.executeUpdate (); tx.Commit (); // ใช้ขอบเขตการประมวลผลธุรกรรมไฮเบอร์เนต ...การเปลี่ยนแปลงนี้จะมีรสชาติที่จำศีล หลังจากการทดสอบผู้เขียนใช้ JDBC API สำหรับการประมวลผลแบบแบตช์ซึ่งสูงกว่าประสิทธิภาพเกือบ 10 เท่ากว่าการใช้ Hibernate API นี่คือประสิทธิภาพที่โดดเด่นของ JDBC อย่างไม่ต้องสงสัย
ในการอัปเดตแบบแบตช์และการลบ Hibernate2 สำหรับการดำเนินการอัปเดตแบบแบตช์ Hibernate ค้นหาข้อมูลที่ตรงตามข้อกำหนดแล้วดำเนินการอัปเดต เช่นเดียวกับการลบแบทช์ ก่อนอื่นค้นหาข้อมูลที่ตรงตามเงื่อนไขจากนั้นทำการลบ
สิ่งนี้มีข้อเสียสองประการที่สำคัญ:
(1): ใช้หน่วยความจำมากมาย
(2): เมื่อประมวลผลข้อมูลจำนวนมากการดำเนินการคำสั่ง UPDATE/DELETE เป็นจำนวนเงินจำนวนมากและคำสั่ง UPDATE/DELETE สามารถใช้งานวัตถุเดียวเท่านั้น เป็นไปได้ว่าประสิทธิภาพของฐานข้อมูลต่ำหากดำเนินการบ่อยครั้ง
หลังจาก Hibernate3 ได้รับการปล่อยตัวการอัปเดต/ลบจำนวนมากได้รับการแนะนำสำหรับการดำเนินการอัปเดต/ลบแบทช์ หลักการคือการดำเนินการอัปเดต/ลบแบทช์ผ่านคำสั่ง HQL ซึ่งคล้ายกับการดำเนินการอัปเดต/ลบแบทช์ของ JDBC มาก ในแง่ของประสิทธิภาพมีการปรับปรุงที่ยอดเยี่ยมเกี่ยวกับการอัพเดทแบทช์/การลบของ Hibernate2
ธุรกรรม tx = session.beginsession (); String hql = "ลบนักเรียน"; Query Query = session.createquery (HQL); ขนาด int = query.executeUpdate (); tx.Commit (); ...
คอนโซลเอาต์พุตเพียงหนึ่งคำสั่งลบไฮเบอร์เนต: ลบจาก t_student การดำเนินการคำสั่งนั้นน้อยกว่าและประสิทธิภาพเกือบจะเหมือนกับการใช้ JDBC มันเป็นวิธีที่ดีในการปรับปรุงประสิทธิภาพ แน่นอนเพื่อให้มีประสิทธิภาพที่ดีขึ้นผู้เขียนแนะนำว่าการอัปเดตแบบแบตช์และการดำเนินการลบยังคงใช้ JDBC วิธีการและจุดความรู้พื้นฐานนั้นเหมือนกับวิธีการแทรกแบทช์ด้านบน 2 ดังนั้นฉันจะไม่อธิบายว่ามันซ้ำซ้อนที่นี่
ที่นี่ฉันมีวิธีการอื่นซึ่งคือการพิจารณาปรับปรุงประสิทธิภาพจากด้านฐานข้อมูลและการโทรขั้นตอนที่เก็บไว้ในด้านโปรแกรมไฮเบอร์เนต ขั้นตอนที่เก็บไว้ทำงานบนฝั่งฐานข้อมูลเร็วขึ้น การอัปเดตแบทช์เป็นตัวอย่างรหัสอ้างอิงจะได้รับ
ก่อนอื่นให้สร้างขั้นตอนที่เก็บไว้ชื่อ BatchUpDatestudent ทางด้านฐานข้อมูล:
สร้างหรือแทนที่ผลิต batchupDatestudent (จำนวน) asbeginupdate นักเรียนชุดอายุ = อายุ+1 โดยที่อายุ> a; end;
รหัสการโทรมีดังนี้:
ธุรกรรม tx = session.beginsession (); การเชื่อมต่อ conn = session.connection (); สตริง pd = "... {เรียก batchupdatestudent (?)}"; callablestatement cstmt = conn.preparecall (pd); cstmt.setint (1, 20); // ตั้งค่าพารามิเตอร์อายุเป็น 20TX.COMMIT ();การสังเกตรหัสข้างต้นนอกจากนี้ยังข้าม Hibernate API และใช้ JDBC API เพื่อเรียกขั้นตอนที่เก็บไว้และใช้ขอบเขตการทำธุรกรรมของ Hibernate ขั้นตอนที่เก็บไว้นั้นเป็นวิธีที่ดีในการปรับปรุงประสิทธิภาพการประมวลผลแบบแบทช์อย่างไม่ต้องสงสัย พวกเขาทำงานโดยตรงกับด้านฐานข้อมูลและในระดับหนึ่งการถ่ายโอนความดันของการประมวลผลแบทช์ไปยังฐานข้อมูล
postscript
บทความนี้กล่าวถึงการดำเนินการประมวลผลแบทช์ของไฮเบอร์เนตและจุดเริ่มต้นคือการพิจารณาปรับปรุงประสิทธิภาพและให้การปรับปรุงประสิทธิภาพเพียงเล็กน้อยเท่านั้น
ไม่ว่าจะใช้วิธีใดก็ตามจะต้องพิจารณาตามเงื่อนไขจริง การจัดหาระบบที่มีประสิทธิภาพและมีเสถียรภาพให้กับผู้ใช้นั้นเป็นสิ่งสำคัญที่สุด
ฉันหวังว่าบทความนี้จะเป็นประโยชน์กับการเขียนโปรแกรมไฮเบอร์เนตของทุกคน