PL/SQL BULK COLLECT VE FORALL
Oracle'ın resmi performans testlerinde, 100.000 satırlık bir tabloyu satır satır işleyen klasik bir cursor döngüsü ile aynı işi BULK COLLECT ve FORALL kombinasyonu yaparak çözen bir blok arasında 30 kat ile 100 kat arasında çalışma süresi farkı ölçülüyor. Bu sadece "biraz daha hızlı" demek değil; saatlerce süren bir gece batch'inin dakikalara inmesi, müşteri raporunun bekleme süresinin saniyenin altına düşmesi anlamına geliyor. Farkın kaynağı da göründüğünden başka bir yerde — yavaş olan SQL motoru değil, PL/SQL ile SQL motoru arasındaki gidiş geliştir.
Context Switch: Yavaşlığın Asıl Sebebi
PL/SQL bloğu içinde her SELECT, INSERT, UPDATE veya DELETE komutu çalıştığında Oracle iki ayrı motor arasında veri taşır: PL/SQL motoru prosedürel kodu yorumlar, SQL motoru ise veri erişimini yapar. Bu iki motor arasındaki her geçiş "context switch" olarak adlandırılır ve sıfır maliyetli değildir. Tek bir geçiş mikrosaniyeler aldığı için fark edilmez; ama 100.000 satırlık bir döngüde 100.000 geçiş yapılırsa toplam süre dramatik biçimde şişer.
Klasik bir FOR rec IN cursor LOOP ... INSERT ... END LOOP yapısı her tur için en az iki geçiş yapar: biri cursor'dan satırı çekmek, biri INSERT'i çalıştırmak. BULK COLLECT ve FORALL bu mantığı tersine çevirir — döngüyü PL/SQL tarafında değil, SQL tarafında toplu olarak çalıştırır.
BULK COLLECT: Satırları Toplu Çekmek
BULK COLLECT INTO ifadesi, bir SELECT sonucunu satır satır cursor üzerinden değil, doğrudan bir collection değişkenine (nested table, varray veya associative array) tek seferde aktarır. Tipik kullanım şu şekildedir:
- Collection tipi tanımı:
TYPE t_emp IS TABLE OF employees%ROWTYPE; - Değişken bildirimi:
v_emps t_emp; - Toplu çekim:
SELECT * BULK COLLECT INTO v_emps FROM employees WHERE department_id = 10; - LIMIT ile kontrol: Büyük tablolarda
FETCH cursor BULK COLLECT INTO v_emps LIMIT 1000;belleği patlatmadan parça parça okur
LIMIT ifadesini atlamak en sık yapılan hatalardan biri. 10 milyon satırlık tabloyu komple PGA'ya yüklemek hem ORA-04030 hatası verir hem de sistemin diğer oturumlarını etkiler. Genel pratik 100 ile 1000 arası bir limit değeridir; bu aralık context switch tasarrufu ile bellek kullanımı arasındaki dengeyi tutturur. Söz dizimi ve sınır değerleri konusunda resmi dil rehberinden faydalanmak doğru kalıpları oturtmayı kolaylaştırır.

FORALL: Toplu DML'in Doğru Yolu
FORALL, bir collection'daki tüm satırlar üzerinde tek bir DML ifadesini çalıştırır — ancak isminin aksine bir döngü değildir. Yapısal olarak şuna benzer:
FORALL i IN 1..v_emps.COUNT
INSERT INTO emp_archive VALUES v_emps(i);Buradaki kritik nokta: PL/SQL motoru tek bir kez SQL motoruna geçiş yapar, ardından SQL motoru tüm INSERT'leri kendi tarafında batch olarak yürütür. Aynı işi FOR i IN 1..v_emps.COUNT LOOP INSERT ...; END LOOP; ile yapsaydık COUNT kadar context switch oluşacaktı.
Somut Benchmark Sonuçları
Oracle 19c üzerinde 1 milyon satırlık bir tablodan başka bir tabloya veri kopyalama testi tipik olarak şu sonuçları üretir:
- Pure SQL (INSERT INTO ... SELECT): ~2 saniye — referans nokta
- BULK COLLECT + FORALL (LIMIT 1000): ~4-6 saniye
- Implicit cursor FOR LOOP + INSERT: ~45-60 saniye
- Explicit cursor + FETCH + INSERT: ~70-90 saniye
Yani satır satır yaklaşıma kıyasla BULK COLLECT/FORALL kombinasyonu 10x-20x, bazı senaryolarda 100x'e varan kazanım sağlıyor. Saf SQL hala en hızlı yöntem; ancak satır başına karmaşık transformasyon, validation veya conditional logic gerektiğinde pure SQL yetersiz kalır. İşte BULK işlemler tam bu boşluğu doldurur.
SAVE EXCEPTIONS ile Hata Yönetimi
FORALL'un standart davranışında herhangi bir satırda hata oluşursa tüm işlem geri alınır. Büyük batch'lerde tek bir bozuk satır için bütün gecenin çöpe gitmesi kabul edilemez. SAVE EXCEPTIONS klozu bu sorunu çözer:
FORALL i IN 1..v_emps.COUNT SAVE EXCEPTIONS
INSERT INTO emp_archive VALUES v_emps(i);İşlem sonunda SQL%BULK_EXCEPTIONS koleksiyonu tüm hata kayıtlarını barındırır. Bu yapıyla ETL süreçlerinde "iyi olanları al, kötüleri logla, işi durdurma" yaklaşımı standart hale gelir. Konuyu derinlemesine kavramak için Oracle PL/SQL eğitimi içeriğinden yararlanabilirsiniz; hem cursor mekaniği hem de exception handling konuları detaylı işleniyor.
RETURNING BULK COLLECT INTO
FORALL ile yapılan toplu DML'lerden geri değer almak da mümkün. INSERT veya UPDATE sonrası üretilen sequence değerlerini, hesaplanmış kolon değerlerini ya da etkilenen primary key'leri tek seferde toplamak için RETURNING klozu kullanılır:
FORALL i IN 1..v_orders.COUNT
INSERT INTO orders VALUES v_orders(i)
RETURNING order_id BULK COLLECT INTO v_new_ids;Bu kalıp, INSERT sonrası child kayıtlarının parent ID'lere ihtiyaç duyduğu klasik senaryolarda satır satır SELECT seq.NEXTVAL çağırmaktan kurtarır.
Ne Zaman Kullanmamalı
BULK COLLECT/FORALL her derde deva değildir. Bazı durumlarda kullanmamak daha doğrudur:
- Pure SQL yeterliyse:
INSERT INTO ... SELECTher zaman daha hızlı; sadece PL/SQL içinde row-level logic varsa BULK'a geç - Çok küçük veri setleri: 10-20 satırda fark yok, kodu karmaşıklaştırmaya değmez
- Trigger içinde: Trigger zaten satır seviyesinde çalıştığı için içeride BULK kullanmak mantıksız
- Single-row DML'lerde: Tek satır INSERT için collection kurmak overhead'dir

Performans optimizasyonu yapan her PL/SQL geliştiricisi için BULK COLLECT ve FORALL kombinasyonu artık opsiyonel değil, temel bir araç. Doğru LIMIT seçimi, SAVE EXCEPTIONS ile dayanıklı hata yönetimi ve RETURNING ile geri değer toplama birlikte ele alındığında, gece batch'lerinin süresi gerçekten ölçülebilir biçimde düşer ve sistem kaynak kullanımı dengelenir.



