Di dalam Oracle, ada mesin virtual SQL (VM) dan PL/SQL VM. Saat Anda perlu berpindah dari satu VM ke VM lainnya, Anda akan dikenakan biaya perubahan konteks. Secara individual, perubahan konteks tersebut relatif cepat, tetapi ketika Anda melakukan pemrosesan baris demi baris, perubahan tersebut dapat menambahkan hingga memperhitungkan sebagian besar waktu yang dihabiskan kode Anda. Saat Anda menggunakan pengikatan massal, Anda memindahkan beberapa baris data dari satu VM ke VM lainnya dengan satu pergeseran konteks, secara signifikan mengurangi jumlah pergeseran konteks, membuat kode Anda lebih cepat.
Ambil, misalnya, kursor eksplisit. Jika saya menulis sesuatu seperti ini
DECLARE
CURSOR c
IS SELECT *
FROM source_table;
l_rec source_table%rowtype;
BEGIN
OPEN c;
LOOP
FETCH c INTO l_rec;
EXIT WHEN c%notfound;
INSERT INTO dest_table( col1, col2, ... , colN )
VALUES( l_rec.col1, l_rec.col2, ... , l_rec.colN );
END LOOP;
END;
maka setiap kali saya menjalankan pengambilan, saya
- Melakukan perubahan konteks dari PL/SQL VM ke SQL VM
- Meminta SQL VM untuk mengeksekusi kursor untuk menghasilkan baris data berikutnya
- Melakukan pergeseran konteks lain dari SQL VM kembali ke PL/SQL VM untuk mengembalikan satu baris data saya
Dan setiap kali saya menyisipkan baris, saya melakukan hal yang sama. Saya menanggung biaya pergeseran konteks untuk mengirimkan satu baris data dari PL/SQL VM ke SQL VM, meminta SQL untuk mengeksekusi INSERT
pernyataan, dan kemudian menimbulkan biaya pergeseran konteks lain kembali ke PL/SQL.
Jika source_table
memiliki 1 juta baris, itu adalah 4 juta pergeseran konteks yang kemungkinan akan menjelaskan sebagian kecil dari waktu yang berlalu dari kode saya. Sebaliknya, jika saya melakukan BULK COLLECT
dengan LIMIT
dari 100, saya dapat menghilangkan 99% dari pergeseran konteks saya dengan mengambil 100 baris data dari SQL VM ke dalam koleksi di PL/SQL setiap kali saya dikenakan biaya pergeseran konteks dan memasukkan 100 baris ke tabel tujuan setiap kali saya menimbulkan pergeseran konteks di sana.
Jika dapat menulis ulang kode saya untuk menggunakan operasi massal
DECLARE
CURSOR c
IS SELECT *
FROM source_table;
TYPE nt_type IS TABLE OF source_table%rowtype;
l_arr nt_type;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_arr LIMIT 100;
EXIT WHEN l_arr.count = 0;
FORALL i IN 1 .. l_arr.count
INSERT INTO dest_table( col1, col2, ... , colN )
VALUES( l_arr(i).col1, l_arr(i).col2, ... , l_arr(i).colN );
END LOOP;
END;
Sekarang, setiap kali saya menjalankan pengambilan, saya mengambil 100 baris data ke dalam koleksi saya dengan satu set pergeseran konteks. Dan setiap kali saya melakukan FORALL
insert, saya memasukkan 100 baris dengan satu set pergeseran konteks. Jika source_table
memiliki 1 juta baris, ini berarti saya telah beralih dari 4 juta pergeseran konteks menjadi 40.000 pergeseran konteks. Jika pergeseran konteks menyumbang, katakanlah, 20% dari waktu yang telah berlalu dari kode saya, saya telah menghilangkan 19,8% dari waktu yang telah berlalu.
Anda dapat meningkatkan ukuran LIMIT
untuk lebih mengurangi jumlah pergeseran konteks tetapi Anda dengan cepat mencapai hukum hasil yang semakin berkurang. Jika Anda menggunakan LIMIT
1000 daripada 100, Anda akan menghilangkan 99,9% dari pergeseran konteks daripada 99%. Itu berarti koleksi Anda menggunakan memori PGA 10x lebih banyak. Dan itu hanya akan menghilangkan 0,18% lebih banyak waktu yang telah berlalu dalam contoh hipotetis kami. Anda dengan sangat cepat mencapai titik di mana memori tambahan yang Anda gunakan menambah lebih banyak waktu daripada yang Anda hemat dengan menghilangkan pergeseran konteks tambahan. Secara umum, sebuah LIMIT
suatu tempat antara 100 dan 1000 kemungkinan akan menjadi sweet spot.
Tentu saja, dalam contoh ini, akan lebih efisien untuk menghilangkan semua perubahan konteks dan melakukan semuanya dalam satu pernyataan SQL
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
Masuk akal untuk menggunakan PL/SQL di tempat pertama jika Anda melakukan semacam manipulasi data dari tabel sumber yang tidak dapat Anda terapkan secara wajar di SQL.
Selain itu, saya menggunakan kursor eksplisit dalam contoh saya dengan sengaja. Jika Anda menggunakan kursor implisit, di Oracle versi terbaru, Anda mendapatkan manfaat dari BULK COLLECT
dengan LIMIT
dari 100 secara implisit. Ada pertanyaan StackOverflow lain yang membahas manfaat kinerja relatif dari kursor implisit dan eksplisit dengan operasi massal yang membahas lebih detail tentang kerutan tertentu.