ORA-01000, kesalahan maksimum-buka-kursor, adalah kesalahan yang sangat umum dalam pengembangan database Oracle. Dalam konteks Java, ini terjadi saat aplikasi mencoba membuka lebih banyak ResultSets daripada kursor yang dikonfigurasi pada instance database.
Penyebab umumnya adalah:
Kesalahan konfigurasi
- Anda memiliki lebih banyak thread dalam aplikasi Anda yang meminta database daripada kursor di DB. Satu kasus adalah saat Anda memiliki koneksi dan kumpulan thread 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.
Larutan:
Kebocoran kursor
- Aplikasi tidak menutup ResultSets (di JDBC) atau kursor (dalam prosedur yang tersimpan di database)
- Solusi : Kebocoran kursor adalah bug; meningkatkan jumlah kursor pada DB hanya akan 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 'Eliminating Leaks'.
Apa itu kursor?
Kursor adalah sumber daya di database yang menyimpan status kueri, khususnya posisi pembaca di ResultSet. Setiap pernyataan SELECT memiliki kursor, dan prosedur tersimpan PL / SQL dapat membuka dan menggunakan kursor sebanyak yang mereka butuhkan. Anda dapat mencari tahu lebih lanjut tentang cursors on Orafaq .
Instance database biasanya melayani beberapa skema berbeda , banyak pengguna berbeda masing-masing dengan beberapa sesi . Untuk melakukan ini, ia memiliki sejumlah kursor tetap yang tersedia untuk semua skema, pengguna dan sesi. Ketika semua kursor terbuka (sedang digunakan) dan permintaan datang yang membutuhkan kursor baru, permintaan gagal dengan kesalahan ORA-010000.
Menemukan dan mengatur jumlah kursor
Nomor tersebut biasanya dikonfigurasi oleh DBA saat penginstalan. Jumlah kursor yang saat ini 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 terkait erat dengan konsep database berikut:
- JDBC Connection adalah representasi klien dari sesi database dan menyediakan transaksi database . Koneksi hanya dapat memiliki satu transaksi yang terbuka pada satu waktu (tetapi transaksi dapat bersarang)
- ResultSet JDBC didukung oleh satu kursor pada database. Saat close () dipanggil di ResultSet, kursor dilepaskan.
- JDBC CallableStatement menjalankan prosedur tersimpan di database, sering kali ditulis dalam PL / SQL. Prosedur tersimpan dapat membuat nol atau lebih kursor, dan dapat mengembalikan kursor sebagai JDBC ResultSet.
JDBC adalah thread safe: Tidak masalah untuk meneruskan berbagai objek JDBC di antara thread.
Misalnya, Anda dapat membuat koneksi dalam satu utas; utas lain dapat menggunakan koneksi ini untuk membuat PreparedStatement dan utas ketiga dapat memproses kumpulan hasil. Batasan utama tunggal adalah bahwa Anda tidak dapat membuka lebih dari satu ResultSet di satu PreparedStatement kapan saja. Lihat Apakah Oracle DB mendukung beberapa operasi (paralel) per koneksi?
Perhatikan bahwa komit database terjadi pada koneksi, dan semua DML (INSERT, UPDATE dan DELETE) pada koneksi itu akan berkomitmen bersama. Oleh karena itu, jika Anda ingin mendukung beberapa transaksi pada saat yang sama, 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 cukup menutup ResultSet tanpa try {} catch {}, itu mungkin gagal dan mencegah Pernyataan ditutup
- Kami ingin mengizinkan pengecualian apa pun yang dimunculkan dalam isi percobaan untuk disebarkan ke pemanggil. Jika Anda memiliki loop, misalnya, membuat dan mengeksekusi Pernyataan, ingatlah untuk menutup setiap Pernyataan dalam loop.
Di Java 7, Oracle telah memperkenalkan antarmuka AutoCloseable yang menggantikan sebagian besar boilerplate Java 6 dengan beberapa gula sintaksis yang bagus.
Memegang objek JDBC
Objek JDBC dapat disimpan dengan aman di variabel lokal, instance objek, dan anggota kelas. Biasanya praktik yang lebih baik adalah:
- Gunakan contoh objek atau anggota kelas untuk menyimpan objek JDBC yang digunakan kembali beberapa kali selama periode yang lebih lama, seperti Connections dan PreparedStatements
- Gunakan variabel lokal untuk ResultSets karena ini diperoleh, diulangi dan kemudian ditutup biasanya dalam lingkup fungsi tunggal.
Namun, ada satu pengecualian: Jika Anda menggunakan EJB, atau container Servlet / JSP, Anda harus mengikuti model threading yang ketat:
- Hanya Server Aplikasi yang membuat utas (yang dengannya ia 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 Anda sendiri atau anggota statis - ini tidak aman di seluruh kluster dan kondisi aneh lainnya, dan Server Aplikasi dapat melakukan hal-hal buruk pada data Anda. Sebagai gantinya gunakan kacang stateful atau database.
- Secara khusus, jangan pernah menahan objek JDBC (Connections, ResultSets, PreparedStatements, dll) melalui pemanggilan jarak jauh yang berbeda - biarkan Application Server mengatur ini. Application Server tidak hanya menyediakan kumpulan koneksi, itu juga menyimpan PreparedStatements Anda.
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 akan mengurangi jumlah bug di perangkat lunak Anda sebelum meninggalkan meja pengembang. Praktik khusus meliputi:
- Pemrograman pasangan , untuk mendidik mereka yang tidak memiliki pengalaman yang memadai
- 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 pustaka yang ada untuk penggabungan 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:
Holdability dan commit
- Jika kemampuan ResultSet adalah ResultSet.CLOSE_CURSORS_OVER_COMMIT, maka ResultSet ditutup ketika metode Connection.commit () dipanggil. Ini bisa disetel menggunakan Connection.setHoldability () atau dengan menggunakan metode Connection.createStatement () yang kelebihan beban.
Logging saat runtime.
- Letakkan pernyataan log yang baik di kode Anda. Ini harus jelas dan dapat dimengerti sehingga pelanggan, staf pendukung, dan rekan tim dapat memahami tanpa pelatihan. Mereka harus singkat dan mencakup pencetakan status / nilai internal variabel dan atribut kunci sehingga Anda dapat melacak logika pemrosesan. Pencatatan yang baik sangat penting untuk men-debug aplikasi, terutama yang telah diterapkan.
Anda dapat menambahkan driver JDBC debugging ke proyek Anda (untuk debugging - tidak benar-benar menerapkannya). 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 masalah potensial
- Memantau database. Pantau aplikasi Anda yang sedang berjalan menggunakan alat seperti fungsi SQL Developer 'Monitor SQL' atau Quest's TOAD . Pemantauan dijelaskan dalam artikel ini . Selama pemantauan, Anda meminta kursor terbuka (misalnya dari tabel v $ sesstat) dan meninjau SQL-nya. Jika jumlah kursor meningkat, dan (yang paling penting) didominasi oleh satu pernyataan SQL yang identik, Anda tahu bahwa Anda memiliki kebocoran dengan SQL tersebut. Telusuri kode Anda dan tinjau.
Pikiran 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 untuk mengumpulkan referensi kapan saja dianggap sesuai (dengan asumsi tidak ada rantai referensi yang kuat ke objek tersebut).
Jika Anda meneruskan ReferenceQueue di konstruktor ke ReferenceQueue lunak atau lemah, objek tersebut ditempatkan di ReferenceQueue saat objek tersebut di-GC ketika terjadi (jika terjadi sama sekali). Dengan pendekatan ini, Anda dapat berinteraksi dengan penyelesaian objek dan Anda dapat menutup atau menyelesaikan objek pada saat itu.
Referensi phantom sedikit lebih aneh; tujuan mereka hanya untuk mengontrol penyelesaian, tetapi Anda tidak akan pernah bisa mendapatkan referensi ke objek asli, jadi akan sulit untuk memanggil metode close () di atasnya.
Namun, jarang sekali merupakan ide yang baik untuk mencoba mengontrol kapan GC dijalankan (Weak, Soft and PhantomReferences memberi tahu Anda setelah fakta bahwa objek diantrekan untuk GC). Faktanya, jika jumlah memori di JVM besar (mis. -Xmx2000m) Anda mungkin tidak akan pernah melakukan GC pada objek, dan Anda masih akan mengalami ORA-01000. Jika memori JVM relatif kecil untuk persyaratan program Anda, Anda mungkin menemukan bahwa objek ResultSet dan PreparedStatement digabungkan segera setelah pembuatan (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.
for (String language : additionalLangs) {