ORA-01000, kesalahan kursor terbuka maksimum, adalah kesalahan yang sangat umum dalam pengembangan database Oracle. Dalam konteks Java, ini terjadi ketika aplikasi mencoba membuka lebih banyak ResultSets daripada kursor yang dikonfigurasi pada instance database.
Penyebab umum adalah:
-
Kesalahan konfigurasi
- Anda memiliki lebih banyak utas dalam aplikasi Anda yang menanyakan database daripada kursor di DB. Satu kasus adalah ketika Anda memiliki koneksi dan kumpulan utas yang lebih besar dari jumlah kursor pada database.
- Anda memiliki banyak pengembang atau aplikasi yang terhubung ke instans DB yang sama (yang mungkin akan menyertakan banyak skema) dan bersama-sama Anda menggunakan terlalu banyak koneksi.
-
Solusi:
- Meningkatkan jumlah kursor pada database (jika sumber daya memungkinkan) atau
- Mengurangi jumlah thread dalam aplikasi.
-
Kebocoran kursor
- Aplikasi tidak menutup ResultSets (di JDBC) atau kursor (dalam prosedur tersimpan di database)
- Solusi :Kebocoran kursor adalah bug; meningkatkan jumlah kursor pada DB hanya menunda kegagalan yang tak terhindarkan. Kebocoran dapat ditemukan menggunakan analisis kode statis, JDBC atau pencatatan tingkat aplikasi, dan pemantauan basis data.
Latar Belakang
Bagian ini menjelaskan beberapa teori di balik kursor dan bagaimana JDBC harus digunakan. Jika Anda tidak perlu mengetahui latar belakangnya, Anda dapat melewati ini dan langsung ke 'Menghilangkan Kebocoran'.
Apa itu kursor?
Kursor adalah sumber daya di database yang menyimpan status kueri, khususnya posisi pembaca dalam ResultSet. Setiap pernyataan SELECT memiliki kursor, dan prosedur tersimpan PL/SQL dapat membuka dan menggunakan kursor sebanyak yang diperlukan. Anda dapat mengetahui lebih lanjut tentang kursor di Orafaq.
Instance database biasanya menyajikan beberapa skema . yang berbeda , banyak pengguna different yang berbeda masing-masing dengan beberapa sesi . Untuk melakukan ini, ia memiliki jumlah kursor tetap yang tersedia untuk semua skema, pengguna, dan sesi. Saat semua kursor terbuka (sedang digunakan) dan permintaan masuk yang memerlukan kursor baru, permintaan gagal dengan kesalahan ORA-010000.
Menemukan dan mengatur jumlah kursor
Nomor tersebut biasanya dikonfigurasi oleh DBA pada saat penginstalan. Jumlah kursor yang sedang digunakan, jumlah maksimum dan konfigurasi dapat diakses di fungsi Administrator di Oracle SQL Developer. Dari SQL dapat diatur dengan:
ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;
Menghubungkan JDBC di JVM dengan kursor di DB
Objek JDBC di bawah ini digabungkan dengan konsep database berikut:
- JDBC Koneksi adalah representasi klien dari database sesi dan menyediakan database transaksi . Koneksi hanya dapat membuka satu transaksi pada satu waktu (tetapi transaksi dapat disarangkan)
- Sebuah JDBC ResultSet didukung oleh satu kursor pada basis data. Saat close() dipanggil pada ResultSet, kursor dilepaskan.
- Sebuah JDBC CallableStatement memanggil prosedur tersimpan pada database, sering ditulis dalam PL/SQL. Prosedur tersimpan dapat membuat nol atau lebih kursor, dan dapat mengembalikan kursor sebagai JDBC ResultSet.
JDBC aman untuk utas:Tidak apa-apa untuk melewatkan berbagai objek JDBC di antara utas.
Misalnya, Anda dapat membuat koneksi dalam satu utas; utas lain dapat menggunakan koneksi ini untuk membuat Pernyataan Siap dan utas ketiga dapat memproses kumpulan hasil. Batasan utama tunggal adalah bahwa Anda tidak dapat membuka lebih dari satu ResultSet pada satu PreparedStatement setiap saat. Lihat Apakah Oracle DB mendukung beberapa operasi (paralel) per koneksi?
Perhatikan bahwa komit database terjadi pada Koneksi, sehingga semua DML (INSERT, UPDATE, dan DELETE) pada koneksi itu akan komit bersama. Oleh karena itu, jika Anda ingin mendukung beberapa transaksi sekaligus, Anda harus memiliki setidaknya satu Koneksi untuk setiap Transaksi bersamaan.
Menutup objek JDBC
Contoh khas dari mengeksekusi ResultSet adalah:
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) { }
}
Perhatikan bagaimana klausa akhirnya mengabaikan pengecualian apa pun yang dimunculkan oleh close():
- Jika Anda hanya menutup ResultSet tanpa mencoba {} catch {}, itu mungkin gagal dan mencegah Pernyataan ditutup
- Kami ingin mengizinkan pengecualian apa pun yang muncul di badan percobaan untuk disebarkan ke pemanggil. Jika Anda memiliki perulangan, misalnya, membuat dan mengeksekusi Pernyataan, ingatlah untuk menutup setiap Pernyataan dalam perulangan.
Di Java 7, Oracle telah memperkenalkan antarmuka AutoCloseable yang menggantikan sebagian besar boilerplate Java 6 dengan beberapa gula sintaksis yang bagus.
Menahan objek JDBC
Objek JDBC dapat disimpan dengan aman di variabel lokal, instance objek, dan anggota kelas. Secara umum, praktik yang lebih baik adalah:
- Gunakan instance objek atau anggota kelas untuk menampung objek JDBC yang digunakan kembali beberapa kali selama periode yang lebih lama, seperti Connections dan PreparedStatements
- Gunakan variabel lokal untuk ResultSets karena variabel ini diperoleh, dilingkarkan, dan kemudian ditutup biasanya dalam lingkup fungsi tunggal.
Namun, ada satu pengecualian:Jika Anda menggunakan EJB, atau wadah Servlet/JSP, Anda harus mengikuti model threading yang ketat:
- Hanya Server Aplikasi yang membuat utas (yang digunakan untuk menangani permintaan masuk)
- Hanya Server Aplikasi yang membuat koneksi (yang Anda peroleh dari kumpulan koneksi)
- Saat menyimpan nilai (status) di antara panggilan, Anda harus sangat berhati-hati. Jangan pernah menyimpan nilai dalam cache atau anggota statis Anda sendiri - ini tidak aman di seluruh cluster dan kondisi aneh lainnya, dan Server Aplikasi dapat melakukan hal buruk pada data Anda. Alih-alih gunakan kacang stateful atau database.
- Khususnya, tidak pernah tahan objek JDBC (Connections, ResultSets, PreparedStatements, dll) melalui pemanggilan jarak jauh yang berbeda - biarkan Server Aplikasi mengelola ini. Server Aplikasi tidak hanya menyediakan kumpulan koneksi, tetapi juga menyimpan pernyataan-pernyataan Anda dalam cache.
Menghilangkan kebocoran
Ada sejumlah proses dan alat yang tersedia untuk membantu mendeteksi dan menghilangkan kebocoran JDBC:
-
Selama pengembangan - menangkap bug lebih awal sejauh ini merupakan pendekatan terbaik:
-
Praktik pengembangan:Praktik pengembangan yang baik harus mengurangi jumlah bug di perangkat lunak Anda sebelum meninggalkan meja pengembang. Praktik khusus meliputi:
- Pemrograman berpasangan, untuk mendidik mereka yang tidak memiliki pengalaman yang cukup
- Ulasan kode karena banyak mata lebih baik dari satu
- Pengujian unit yang berarti Anda dapat menggunakan setiap dan semua basis kode Anda dari alat uji yang membuat mereproduksi kebocoran menjadi sepele
- Gunakan perpustakaan yang ada untuk penyatuan koneksi daripada membuat sendiri
-
Analisis Kode Statis:Gunakan alat seperti Findbugs yang sangat baik untuk melakukan analisis kode statis. Ini mengambil banyak tempat di mana close() belum ditangani dengan benar. Findbugs memiliki plugin untuk Eclipse, tetapi juga berjalan mandiri untuk satu kali, memiliki integrasi ke Jenkins CI dan alat build lainnya
-
-
Saat runtime:
-
Daya tahan dan komitmen
- Jika daya tahan ResultSet adalah ResultSet.CLOSE_CURSORS_OVER_COMMIT, maka ResultSet ditutup saat metode Connection.commit() dipanggil. Ini dapat diatur menggunakan Connection.setHoldability() atau dengan menggunakan metode Connection.createStatement() yang kelebihan beban.
-
Masuk saat runtime.
- Masukkan pernyataan log yang baik dalam kode Anda. Ini harus jelas dan dapat dimengerti sehingga pelanggan, staf pendukung, dan rekan tim dapat memahami tanpa pelatihan. Mereka harus singkat dan menyertakan pencetakan status/nilai internal variabel dan atribut kunci sehingga Anda dapat melacak logika pemrosesan. Logging yang baik adalah dasar untuk men-debug aplikasi, terutama yang telah di-deploy.
-
Anda dapat menambahkan driver JDBC debugging ke proyek Anda (untuk debugging - jangan gunakan itu). Salah satu contoh (saya belum pernah menggunakannya) adalah log4jdbc. Anda kemudian perlu melakukan beberapa analisis sederhana pada file ini untuk melihat eksekusi mana yang tidak memiliki penutupan yang sesuai. Menghitung pembukaan dan penutupan harus menyoroti jika ada potensi masalah
- Memantau basis data. Pantau aplikasi Anda yang sedang berjalan menggunakan alat seperti fungsi 'Monitor SQL' Pengembang SQL atau TOAD Quest. Pemantauan dijelaskan dalam artikel ini. Selama pemantauan, Anda menanyakan kursor yang terbuka (misalnya dari tabel v$sesstat) dan meninjau SQL-nya. Jika jumlah kursor meningkat, dan (yang paling penting) menjadi didominasi oleh satu pernyataan SQL yang identik, Anda tahu bahwa Anda memiliki kebocoran dengan SQL itu. Telusuri kode dan ulasan Anda.
-
Pemikiran lain
Dapatkah Anda menggunakan WeakReferences untuk menangani penutupan koneksi?
Referensi lemah dan lunak adalah cara yang memungkinkan Anda untuk mereferensikan objek dengan cara yang memungkinkan JVM mengumpulkan sampah referensi kapan saja dianggap cocok (dengan asumsi tidak ada rantai referensi yang kuat ke objek itu).
Jika Anda meneruskan ReferenceQueue di konstruktor ke Referensi lunak atau lemah, objek ditempatkan di ReferenceQueue saat objek di-GC ketika itu terjadi (jika itu terjadi sama sekali). Dengan pendekatan ini, Anda dapat berinteraksi dengan finalisasi objek dan Anda dapat menutup atau menyelesaikan objek pada saat itu.
Referensi phantom sedikit lebih aneh; tujuannya hanya untuk mengontrol finalisasi, tetapi Anda tidak akan pernah bisa mendapatkan referensi ke objek aslinya, jadi akan sulit untuk memanggil metode close() di atasnya.
Namun, jarang merupakan ide yang baik untuk mencoba mengontrol saat GC dijalankan (Lemah, Lembut, dan PhantomReferences memberi tahu Anda setelah fakta bahwa objek tersebut diantrekan untuk GC). Bahkan, jika jumlah memori dalam JVM besar (misalnya -Xmx2000m), Anda mungkin tidak pernah GC objek, dan Anda masih akan mengalami ORA-01000. Jika memori JVM relatif kecil untuk kebutuhan program Anda, Anda mungkin menemukan bahwa objek ResultSet dan PreparedStatement di-GC segera setelah dibuat (sebelum Anda dapat membacanya), yang kemungkinan besar akan menggagalkan program Anda.
TL;DR: Mekanisme referensi yang lemah bukanlah cara yang baik untuk mengelola dan menutup objek Statement dan ResultSet.