Özet: Veritabanı üzerinde çok fazla veri üzerinde işlem yapacaksanız(örneğin çok fazla kayıt insert edecekseniz) sürecinizi çok hızlandıracak “Bulk Collect” yöntemi anlatılıyor.

Çok fazla veri üzerinde işlem yapmamız gerekiyorsa Oracle’ın “Bulk Collect” özelliğinin nimetlerinden yararlanabiliriz. Aşağıdaki örnek kullanımı step by step anlatalım, test edelim;

— step -1)

Öncelikle örnek bir dataya ihtiyacımız var. Bunu örneğin DBA_TAB_SUBPARTITIONS dic. view inden elde edebilirim (siz herhangi bir veriyle doldurabilirsiniz tablonuzu).  Bu view in yapısına benzer “orhan1″ ve “orhan2″ isimlerinde tablo create ediyorum. orhan1 tablosunu

CREATE TABLE orhan2  --sonra da orhan1 tablosu için yap.
 (
 TABLE_NAME VARCHAR2(30 BYTE) NOT NULL,
 PARTITION_NAME VARCHAR2(30 BYTE),
 SUBPARTITION_NAME VARCHAR2(30 BYTE),
 SUBPARTITION_POSITION NUMBER,
 TABLESPACE_NAME VARCHAR2(30 BYTE) NOT NULL,
 PCT_FREE NUMBER NOT NULL,
 PCT_USED NUMBER,
 INI_TRANS NUMBER NOT NULL,
 MAX_TRANS NUMBER NOT NULL,
 INITIAL_EXTENT NUMBER,
 NEXT_EXTENT NUMBER,
 MIN_EXTENT NUMBER,
 MAX_EXTENT NUMBER,
 MAX_SIZE NUMBER,
 PCT_INCREASE NUMBER,
 FREELISTS NUMBER,
 FREELIST_GROUPS NUMBER,
 LOGGING VARCHAR2(3 BYTE),
 COMPRESSION VARCHAR2(8 BYTE),
 COMPRESS_FOR VARCHAR2(12 BYTE),
 NUM_ROWS NUMBER,
 BLOCKS NUMBER,
 EMPTY_BLOCKS NUMBER,
 AVG_SPACE NUMBER,
 CHAIN_CNT NUMBER,
 AVG_ROW_LEN NUMBER,
 SAMPLE_SIZE NUMBER,
 LAST_ANALYZED DATE,
 BUFFER_POOL VARCHAR2(7 BYTE),
 FLASH_CACHE VARCHAR2(7 BYTE),
 CELL_FLASH_CACHE VARCHAR2(7 BYTE),
 GLOBAL_STATS VARCHAR2(3 BYTE),
 USER_STATS VARCHAR2(3 BYTE),
 INTERVAL VARCHAR2(3 BYTE),
 SEGMENT_CREATED VARCHAR2(3 BYTE)
 );

— step -2)

aşağıdaki gibi bir döngü ile datayı çoğaltıyorum;

begin
 for j in 1..10 loop
 for i in 1..10 loop
 insert into orhan2 
 select TABLE_NAME, PARTITION_NAME, SUBPARTITION_NAME, SUBPARTITION_POSITION,
 TABLESPACE_NAME, PCT_FREE, PCT_USED, INI_TRANS, MAX_TRANS,
 INITIAL_EXTENT, NEXT_EXTENT, MIN_EXTENT, MAX_EXTENT, MAX_SIZE,
 PCT_INCREASE, FREELISTS, FREELIST_GROUPS, LOGGING, COMPRESSION,
 COMPRESS_FOR, NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE,
 CHAIN_CNT, AVG_ROW_LEN, SAMPLE_SIZE, LAST_ANALYZED, BUFFER_POOL,
 FLASH_CACHE, CELL_FLASH_CACHE, GLOBAL_STATS, USER_STATS, INTERVAL,
 SEGMENT_CREATED from DBA_TAB_SUBPARTITIONS;
 end loop;
 commit;
 end loop;
 end;

— step -3)

6 milyondan fazla data var elimde şuan. Birindeki dataları diğer tabloya aktarmanın en kısa yollarını arıyorum.

Test için yeterli bir data;

select count(*) from orhan2;

— 6854700
— step -4)

Imlicit cursor (for döngüsü ile yapılan işlem) ile klasik yöntemde öncelikle orhan2 tablo datalarını orhan1 tablosuna insert ederek süresini ölçeceğiz;

----------Implicit (For) Cursor -----------
 BEGIN
 DBMS_OUTPUT.put_line ('Start:' || SYSTIMESTAMP);
 --for a in 1 .. 1000
 --loop
 FOR x IN (SELECT * FROM orhan2)
 LOOP
 INSERT INTO orhan1
 (TABLE_NAME, PARTITION_NAME, SUBPARTITION_NAME, SUBPARTITION_POSITION,
 TABLESPACE_NAME, PCT_FREE, PCT_USED, INI_TRANS, MAX_TRANS,
 INITIAL_EXTENT, NEXT_EXTENT, MIN_EXTENT, MAX_EXTENT, MAX_SIZE,
 PCT_INCREASE, FREELISTS, FREELIST_GROUPS, LOGGING, COMPRESSION,
 COMPRESS_FOR, NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE,
 CHAIN_CNT, AVG_ROW_LEN, SAMPLE_SIZE, LAST_ANALYZED, BUFFER_POOL,
 FLASH_CACHE, CELL_FLASH_CACHE, GLOBAL_STATS, USER_STATS, INTERVAL,
 SEGMENT_CREATED)
 VALUES
 (x.TABLE_NAME, x.PARTITION_NAME, x.SUBPARTITION_NAME, x.SUBPARTITION_POSITION,
 x.TABLESPACE_NAME, x.PCT_FREE, x.PCT_USED, x.INI_TRANS, x.MAX_TRANS,
 x.INITIAL_EXTENT, x.NEXT_EXTENT, x.MIN_EXTENT, x.MAX_EXTENT, x.MAX_SIZE,
 x.PCT_INCREASE, x.FREELISTS, x.FREELIST_GROUPS, x.LOGGING, x.COMPRESSION,
 x.COMPRESS_FOR, x.NUM_ROWS, x.BLOCKS, x.EMPTY_BLOCKS, x.AVG_SPACE,
 x.CHAIN_CNT, x.AVG_ROW_LEN, x.SAMPLE_SIZE, x.LAST_ANALYZED, x.BUFFER_POOL,
 x.FLASH_CACHE, x.CELL_FLASH_CACHE, x.GLOBAL_STATS, x.USER_STATS, x.INTERVAL,
 x.SEGMENT_CREATED);
 END LOOP;
 COMMIT;
 -- end loop;
 DBMS_OUTPUT.put_line ('End:' || SYSTIMESTAMP);
 EXCEPTION
 WHEN NO_DATA_FOUND THEN
 DBMS_OUTPUT.put_line (SQLERRM);
 WHEN OTHERS THEN
 DBMS_OUTPUT.put_line (SQLERRM);
 END;

Start:12/03/2017 22:18:39,596217000 +03:00
End:12/03/2017 22:48:51,845936000 +03:00

30 dk. sürdü

Data sayımız aynı, zaten farklı beklemiyoruz;

select count(*) from orhan1;

— 6854700
— step 5)

ikinci testimiz için orhan1 tablosunu truncate ederek içini boşaltıyoruz;

truncate table orhan1;

 

— step 6)

Şimdi “Bulk Collect” özelliğini kullanarak insert edeceğiz ve süreleri karşılaştıracağız.
Aşağıdaki örnek plsql’i kendi kodunuza göre uyarlayarak kullanabilirsiniz. Bu yöntem sayesinde vereceğimiz limit’deki 10.000, 100.000 gibi değer kadar toplu olarak her defasında veriyi yakalayacaktır (fetch) ve çok daha hızlı sonuç alabileceğiz.

 
 DECLARE

CURSOR cur_1 IS
 SELECT * FROM orhan2;

TYPE fetch_1 IS TABLE OF cur_1%ROWTYPE;

v_1 fetch_1;

BEGIN
 DBMS_OUTPUT.put_line ('Start:' || SYSTIMESTAMP);
 OPEN cur_1;
 LOOP
 FETCH cur_1 BULK COLLECT INTO v_1 LIMIT 10000;

FORALL i IN 1..v_1.COUNT
 INSERT INTO orhan1 VALUES v_1(i);

EXIT WHEN cur_1%NOTFOUND;
 END LOOP;
 CLOSE cur_1;

COMMIT;
 DBMS_OUTPUT.put_line ('End:' || SYSTIMESTAMP);

EXCEPTION
 WHEN NO_DATA_FOUND THEN
 DBMS_OUTPUT.put_line (SQLERRM);
 WHEN OTHERS THEN
 DBMS_OUTPUT.put_line (SQLERRM);

END;

Start:12/03/2017 23:39:56,790871000 +03:00
End:12/03/2017 23:41:38,890317000 +03:00

Muhteşem bir sonuç; 1 dk. 42 saniyede bitti! 🙂

Klasik yöntemde çok fazla kaydı insert etmek yerine “Bulk collect” ile çok daha kısa sürede yapabiliriz, benzer şekilde update, delete işlemlerimiz için de bunu uyarlayabilirsiniz.

Faydası olması dileklerimizle…