ORA-01000 ข้อผิดพลาดเคอร์เซอร์เปิดสูงสุดเป็นข้อผิดพลาดที่พบบ่อยมากในการพัฒนาฐานข้อมูล Oracle ในบริบทของ Java จะเกิดขึ้นเมื่อแอ็พพลิเคชันพยายามเปิด ResultSets มากกว่าที่กำหนดเคอร์เซอร์บนอินสแตนซ์ฐานข้อมูล
สาเหตุทั่วไปคือ:
ข้อผิดพลาดในการกำหนดค่า
- คุณมีเธรดในแอ็พพลิเคชันของคุณในการสอบถามฐานข้อมูลมากกว่าเคอร์เซอร์บน DB กรณีหนึ่งคือคุณมีการเชื่อมต่อและเธรดพูลที่มีขนาดใหญ่กว่าจำนวนเคอร์เซอร์บนฐานข้อมูล
- คุณมีนักพัฒนาหรือแอปพลิเคชันมากมายที่เชื่อมต่อกับอินสแตนซ์ DB เดียวกัน (ซึ่งอาจรวมถึงสคีมาจำนวนมาก) และคุณใช้การเชื่อมต่อร่วมกันมากเกินไป
สารละลาย:
เคอร์เซอร์รั่ว
- แอ็พพลิเคชันไม่ได้ปิด ResultSets (ใน JDBC) หรือเคอร์เซอร์ (ในโพรซีเดอร์ที่เก็บไว้บนฐานข้อมูล)
- วิธีแก้ไข : การรั่วไหลของเคอร์เซอร์เป็นจุดบกพร่อง การเพิ่มจำนวนเคอร์เซอร์บน DB เพียงแค่ชะลอความล้มเหลวที่หลีกเลี่ยงไม่ได้ การรั่วไหลที่สามารถพบได้โดยใช้การวิเคราะห์รหัสคง , JDBCหรือเข้าสู่ระบบระดับโปรแกรมประยุกต์และการตรวจสอบฐานข้อมูล
พื้นหลัง
ส่วนนี้อธิบายถึงทฤษฎีบางประการที่อยู่เบื้องหลังเคอร์เซอร์และวิธีใช้ JDBC หากคุณไม่จำเป็นต้องรู้พื้นหลังคุณสามารถข้ามสิ่งนี้และตรงไปที่ 'การกำจัดการรั่วไหล'
เคอร์เซอร์คืออะไร?
เคอร์เซอร์เป็นทรัพยากรบนฐานข้อมูลที่เก็บสถานะของแบบสอบถามโดยเฉพาะตำแหน่งที่ผู้อ่านอยู่ใน ResultSet คำสั่ง SELECT แต่ละคำสั่งมีเคอร์เซอร์และโพรซีเดอร์ที่จัดเก็บ PL / SQL สามารถเปิดและใช้เคอร์เซอร์ได้มากเท่าที่ต้องการ คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับเคอร์เซอร์บนOrafaq
อินสแตนซ์ฐานข้อมูลมักจะทำหน้าที่แตกต่างกันหลายสกีมาที่แตกต่างกันหลายผู้ใช้แต่ละคนมีหลายครั้ง ในการดำเนินการนี้จึงมีเคอร์เซอร์จำนวนคงที่สำหรับสคีมาผู้ใช้และเซสชันทั้งหมด เมื่อเคอร์เซอร์ทั้งหมดเปิดอยู่ (ใช้งานอยู่) และมีการร้องขอที่ต้องใช้เคอร์เซอร์ใหม่คำขอจะล้มเหลวด้วยข้อผิดพลาด ORA-010000
การค้นหาและตั้งค่าจำนวนเคอร์เซอร์
โดยปกติ DBA จะกำหนดค่าหมายเลขไว้ในการติดตั้ง จำนวนของเคอร์เซอร์ใช้อยู่ในปัจจุบันจำนวนสูงสุดและการกำหนดค่าที่สามารถเข้าถึงได้ในการทำงานของผู้ดูแลระบบในOracle SQL Developer จาก SQL สามารถตั้งค่าด้วย:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
เกี่ยวข้องกับ JDBC ใน JVM กับเคอร์เซอร์บน DB
วัตถุ JDBC ด้านล่างเชื่อมโยงอย่างแน่นหนากับแนวคิดฐานข้อมูลต่อไปนี้:
- JDBC เชื่อมต่อเป็นตัวแทนลูกค้าของฐานข้อมูลเซสชั่นและให้ฐานข้อมูลการทำธุรกรรม การเชื่อมต่อสามารถเปิดธุรกรรมได้เพียงรายการเดียวในแต่ละครั้ง (แต่สามารถซ้อนธุรกรรมได้)
- JDBC ResultSetได้รับการสนับสนุนโดยเคอร์เซอร์เดียวบนฐานข้อมูล เมื่อเรียก close () บน ResultSet เคอร์เซอร์จะถูกปล่อย
- JDBC CallableStatementเรียกใช้โพรซีเดอร์ที่เก็บไว้บนฐานข้อมูลซึ่งมักเขียนด้วย PL / SQL กระบวนงานที่เก็บไว้สามารถสร้างเคอร์เซอร์เป็นศูนย์หรือมากกว่าและสามารถส่งคืนเคอร์เซอร์เป็น JDBC ResultSet
JDBC เป็นเธรดที่ปลอดภัย: ค่อนข้างโอเคที่จะส่งผ่านอ็อบเจ็กต์ JDBC ต่างๆระหว่างเธรด
ตัวอย่างเช่นคุณสามารถสร้างการเชื่อมต่อในเธรดเดียว เธรดอื่นสามารถใช้การเชื่อมต่อนี้เพื่อสร้าง PreparedStatement และเธรดที่สามสามารถประมวลผลชุดผลลัพธ์ ข้อ จำกัด หลักประการเดียวคือคุณไม่สามารถเปิด ResultSet ได้มากกว่าหนึ่งรายการบน PreparedStatement เดียวได้ตลอดเวลา โปรดดูOracle DB รองรับการดำเนินการหลายอย่าง (ขนาน) ต่อการเชื่อมต่อหรือไม่
โปรดทราบว่าการคอมมิตฐานข้อมูลเกิดขึ้นบนการเชื่อมต่อดังนั้น DML ทั้งหมด (INSERT, UPDATE และ DELETE) บนการเชื่อมต่อนั้นจะคอมมิตเข้าด้วยกัน ดังนั้นหากคุณต้องการรองรับธุรกรรมหลายรายการในเวลาเดียวกันคุณต้องมีการเชื่อมต่ออย่างน้อยหนึ่งรายการสำหรับแต่ละธุรกรรมพร้อมกัน
การปิดวัตถุ JDBC
ตัวอย่างทั่วไปของการดำเนินการ ResultSet คือ:
Statement stmt = conn.createStatement();
try {
ResultSet rs = stmt.executeQuery( "SELECT FULL_NAME FROM EMP" );
try {
while ( rs.next() ) {
System.out.println( "Name: " + rs.getString("FULL_NAME") );
}
} finally {
try { rs.close(); } catch (Exception ignore) { }
}
} finally {
try { stmt.close(); } catch (Exception ignore) { }
}
สังเกตว่าประโยคสุดท้ายจะละเว้นข้อยกเว้นใด ๆ ที่เกิดจากการปิด ():
- หากคุณเพียงแค่ปิด ResultSet โดยไม่ได้ลอง {} catch {} อาจล้มเหลวและป้องกันไม่ให้ Statement ถูกปิด
- เราต้องการอนุญาตให้มีข้อยกเว้นใด ๆ ที่เกิดขึ้นในเนื้อหาของการพยายามเผยแพร่ไปยังผู้โทร หากคุณมีการวนซ้ำตัวอย่างเช่นการสร้างและดำเนินการคำสั่งอย่าลืมปิดแต่ละคำสั่งภายในลูป
ใน Java 7 นั้น Oracle ได้เปิดตัวอินเทอร์เฟซ AutoCloseableซึ่งแทนที่แผ่นต้นแบบ Java 6 ส่วนใหญ่ด้วยน้ำตาลซินแทติกที่ดี
ถือวัตถุ JDBC
อ็อบเจ็กต์ JDBC สามารถถูกเก็บไว้อย่างปลอดภัยในตัวแปรโลคัลอินสแตนซ์ออบเจ็กต์และสมาชิกคลาส โดยทั่วไปแล้วแนวทางปฏิบัติที่ดีกว่าคือ:
- ใช้อ็อบเจ็กต์อินสแตนซ์หรือสมาชิกคลาสเพื่อเก็บอ็อบเจ็กต์ JDBC ที่ใช้ซ้ำหลายครั้งในช่วงเวลาที่ยาวนานขึ้นเช่น Connections และ PreparedStatements
- ใช้ตัวแปรท้องถิ่นสำหรับ ResultSets เนื่องจากได้รับสิ่งเหล่านี้วนซ้ำแล้วปิดโดยทั่วไปภายในขอบเขตของฟังก์ชันเดียว
อย่างไรก็ตามมีข้อยกเว้นประการหนึ่ง: หากคุณใช้ EJB หรือคอนเทนเนอร์ Servlet / JSP คุณต้องทำตามแบบจำลองเธรดที่เข้มงวด:
- เฉพาะ Application Server เท่านั้นที่สร้างเธรด (ซึ่งจัดการกับคำร้องขอที่เข้ามา)
- เฉพาะ Application Server เท่านั้นที่สร้างการเชื่อมต่อ (ซึ่งคุณได้รับจากพูลการเชื่อมต่อ)
- เมื่อบันทึกค่า (สถานะ) ระหว่างการโทรคุณต้องระวังให้มาก อย่าเก็บค่าในแคชของคุณเองหรือสมาชิกแบบคงที่ - สิ่งนี้ไม่ปลอดภัยในคลัสเตอร์และเงื่อนไขแปลก ๆ อื่น ๆ และ Application Server อาจทำสิ่งเลวร้ายกับข้อมูลของคุณ ใช้เมล็ดถั่วหรือฐานข้อมูลแทน
- โดยเฉพาะอย่างยิ่งอย่าถือวัตถุ JDBC (Connections, ResultSets, PreparedStatements ฯลฯ ) เหนือการเรียกใช้แบบรีโมตที่แตกต่างกัน - ปล่อยให้ Application Server จัดการสิ่งนี้ Application Server ไม่เพียง แต่มีพูลการเชื่อมต่อเท่านั้น แต่ยังเก็บ PreparedStatements ของคุณไว้ด้วย
กำจัดการรั่วไหล
มีกระบวนการและเครื่องมือมากมายสำหรับช่วยตรวจจับและกำจัดการรั่วไหลของ JDBC:
ระหว่างการพัฒนา - การจับจุดบกพร่อง แต่เนิ่นๆเป็นแนวทางที่ดีที่สุด:
แนวทางปฏิบัติในการพัฒนา: แนวทางปฏิบัติในการพัฒนาที่ดีควรลดจำนวนข้อบกพร่องในซอฟต์แวร์ของคุณก่อนที่จะออกจากโต๊ะทำงานของนักพัฒนาซอฟต์แวร์ แนวทางปฏิบัติเฉพาะ ได้แก่ :
- จับคู่การเขียนโปรแกรมเพื่อให้ความรู้แก่ผู้ที่ไม่มีประสบการณ์เพียงพอ
- โค๊ดรีวิวเพราะตาเยอะดีกว่ากัน
- การทดสอบหน่วยซึ่งหมายความว่าคุณสามารถใช้ฐานรหัสใดก็ได้และทั้งหมดจากเครื่องมือทดสอบซึ่งทำให้การรั่วไหลเกิดขึ้นเป็นเรื่องเล็กน้อย
- ใช้ไลบรารีที่มีอยู่เพื่อการเชื่อมต่อร่วมกันแทนที่จะสร้างขึ้นเอง
การวิเคราะห์โค้ดแบบคงที่: ใช้เครื่องมือเช่นFindbugs ที่ยอดเยี่ยมเพื่อทำการวิเคราะห์โค้ดแบบคงที่ สิ่งนี้เลือกสถานที่หลายแห่งที่ปิด () ไม่ได้รับการจัดการอย่างถูกต้อง Findbugs มีปลั๊กอินสำหรับ Eclipse แต่ยังทำงานแบบสแตนด์อโลนสำหรับการใช้งานครั้งเดียวมีการรวมเข้ากับ Jenkins CI และเครื่องมือสร้างอื่น ๆ
ที่รันไทม์:
ความสามารถในการถือครองและการกระทำ
- ถ้าความสามารถในการถือครอง ResultSet เป็น ResultSet.CLOSE_CURSORS_OVER_COMMIT ดังนั้น ResultSet จะปิดเมื่อมีการเรียกวิธี Connection.commit () สามารถตั้งค่าได้โดยใช้ Connection.setHoldability () หรือโดยใช้เมธอด Connection.createStatement () ที่โอเวอร์โหลด
เข้าสู่ระบบที่รันไทม์
- ใส่ข้อความบันทึกที่ดีในรหัสของคุณ สิ่งเหล่านี้ควรชัดเจนและเข้าใจได้เพื่อให้ลูกค้าพนักงานสนับสนุนและเพื่อนร่วมทีมเข้าใจได้โดยไม่ต้องฝึกอบรม ควรสั้นและรวมถึงการพิมพ์ค่าสถานะ / ภายในของตัวแปรและแอตทริบิวต์หลักเพื่อให้คุณสามารถติดตามตรรกะการประมวลผลได้ การบันทึกที่ดีเป็นพื้นฐานในการดีบักแอปพลิเคชันโดยเฉพาะอย่างยิ่งที่ได้รับการปรับใช้
คุณสามารถเพิ่มไดรเวอร์ JDBC การดีบักลงในโปรเจ็กต์ของคุณ (สำหรับการดีบัก - อย่าปรับใช้จริง) ตัวอย่างหนึ่ง (ผมไม่ได้ใช้มัน) เป็นlog4jdbc จากนั้นคุณต้องทำการวิเคราะห์ง่ายๆในไฟล์นี้เพื่อดูว่าการดำเนินการใดที่ไม่มีการปิดที่เกี่ยวข้อง การนับการเปิดและปิดควรเน้นว่ามีปัญหาหรือไม่
- การตรวจสอบฐานข้อมูล ตรวจสอบโปรแกรมที่ทำงานของคุณโดยใช้เครื่องมือเช่น SQL Developer ฟังก์ชั่น 'จอภาพ SQL หรือคางคกเควส การตรวจสอบที่อธิบายไว้ในบทความนี้ ในระหว่างการตรวจสอบคุณสอบถามเคอร์เซอร์แบบเปิด (เช่นจากตาราง v $ sesstat) และตรวจสอบ SQL ของพวกเขา หากจำนวนเคอร์เซอร์เพิ่มขึ้นและ (ที่สำคัญที่สุด) ถูกครอบงำโดยคำสั่ง SQL ที่เหมือนกันคุณจะรู้ว่า SQL รั่ว ค้นหารหัสของคุณและตรวจสอบ
ความคิดอื่น ๆ
คุณสามารถใช้ WeakReferences เพื่อจัดการการปิดการเชื่อมต่อได้หรือไม่?
การอ้างอิงที่อ่อนแอและนุ่มนวลเป็นวิธีที่ช่วยให้คุณสามารถอ้างอิงวัตถุในลักษณะที่อนุญาตให้ JVM เก็บขยะรวบรวมข้อมูลอ้างอิงได้ตลอดเวลาที่เห็นว่าเหมาะสม (สมมติว่าไม่มีโซ่อ้างอิงที่รัดกุมสำหรับวัตถุนั้น)
หากคุณส่ง ReferenceQueue ในคอนสตรัคเตอร์ไปยังการอ้างอิงที่อ่อนหรืออ่อนวัตถุจะถูกวางไว้ใน ReferenceQueue เมื่อวัตถุถูก GC'ed เมื่อเกิดขึ้น (หากเกิดขึ้นเลย) ด้วยวิธีนี้คุณสามารถโต้ตอบกับการสรุปของวัตถุและคุณสามารถปิดหรือสิ้นสุดวัตถุได้ในขณะนั้น
การอ้างอิงของ Phantom ค่อนข้างแปลกกว่า วัตถุประสงค์ของพวกเขาคือเพียงเพื่อควบคุมการสรุป แต่คุณไม่สามารถรับการอ้างอิงไปยังวัตถุดั้งเดิมได้ดังนั้นจึงเป็นการยากที่จะเรียกเมธอด close () บนมัน
อย่างไรก็ตามไม่ค่อยเป็นความคิดที่ดีที่จะพยายามควบคุมเมื่อ GC ทำงาน (Weak, Soft และ PhantomReferences แจ้งให้คุณทราบหลังจากที่วัตถุนั้นถูกกำหนดไว้สำหรับ GC) ในความเป็นจริงหากจำนวนหน่วยความจำใน JVM มีขนาดใหญ่ (เช่น -Xmx2000m) คุณอาจไม่เคย GC วัตถุและคุณจะยังคงได้สัมผัสกับ ORA-01000 หากหน่วยความจำ JVM มีขนาดเล็กเมื่อเทียบกับข้อกำหนดของโปรแกรมของคุณคุณอาจพบว่าอ็อบเจ็กต์ ResultSet และ PreparedStatement เป็น GCed ทันทีหลังจากสร้าง (ก่อนที่คุณจะสามารถอ่านได้) ซึ่งอาจทำให้โปรแกรมของคุณล้มเหลว
TL; DR:กลไกการอ้างอิงที่อ่อนแอไม่ใช่วิธีที่ดีในการจัดการและปิดวัตถุ Statement และ ResultSet
for (String language : additionalLangs) {