All'interno di Oracle sono presenti una macchina virtuale (VM) SQL e una VM PL/SQL. Quando è necessario passare da una macchina virtuale all'altra, si sostiene il costo di un cambio di contesto. Singolarmente, questi cambiamenti di contesto sono relativamente rapidi, ma quando si esegue l'elaborazione riga per riga, possono sommarsi per rappresentare una frazione significativa del tempo impiegato dal codice. Quando utilizzi le associazioni in blocco, sposti più righe di dati da una macchina virtuale all'altra con un unico cambio di contesto, riducendo notevolmente il numero di cambiamenti di contesto, rendendo il tuo codice più veloce.
Prendi, ad esempio, un cursore esplicito. Se scrivo qualcosa del genere
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;
quindi ogni volta che eseguo il recupero, lo sono
- Esecuzione di un cambio di contesto dalla VM PL/SQL alla VM SQL
- Chiedere alla VM SQL di eseguire il cursore per generare la riga di dati successiva
- Esecuzione di un altro spostamento del contesto dalla VM SQL alla VM PL/SQL per restituire la mia singola riga di dati
E ogni volta che inserisco una riga, sto facendo la stessa cosa. Sto sostenendo il costo di un cambio di contesto per spedire una riga di dati dalla VM PL/SQL alla VM SQL, chiedendo all'SQL di eseguire INSERT
istruzione e quindi sostenere il costo di un altro passaggio di contesto a PL/SQL.
Se source_table
ha 1 milione di righe, ovvero 4 milioni di spostamenti di contesto che probabilmente rappresenteranno una frazione ragionevole del tempo trascorso del mio codice. Se invece faccio un BULK COLLECT
con un LIMIT
di 100, posso eliminare il 99% dei miei spostamenti di contesto recuperando 100 righe di dati dalla VM SQL in una raccolta in PL/SQL ogni volta che sostengo il costo di un cambio di contesto e inserendo 100 righe nella tabella di destinazione ogni volta che subire un cambiamento di contesto lì.
Se posso riscrivere il mio codice per utilizzare le operazioni di massa
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;
Ora, ogni volta che eseguo il recupero, recupero 100 righe di dati nella mia raccolta con un unico set di cambiamenti di contesto. E ogni volta che faccio il mio FORALL
inserisci, sto inserendo 100 righe con un unico set di spostamenti di contesto. Se source_table
ha 1 milione di righe, questo significa che sono passato da 4 milioni di cambi di contesto a 40.000 cambiamenti di contesto. Se i cambiamenti di contesto hanno rappresentato, ad esempio, il 20% del tempo trascorso del mio codice, ho eliminato il 19,8% del tempo trascorso.
Puoi aumentare la dimensione del LIMIT
per ridurre ulteriormente il numero di cambiamenti di contesto, ma hai rapidamente colpito la legge dei rendimenti decrescenti. Se hai utilizzato un LIMIT
di 1000 anziché 100, elimineresti il 99,9% dei cambiamenti di contesto anziché il 99%. Ciò significherebbe che la tua raccolta utilizzava 10 volte più memoria PGA, tuttavia. E nel nostro ipotetico esempio eliminerebbe solo lo 0,18% in più di tempo trascorso. Raggiungi molto rapidamente un punto in cui la memoria aggiuntiva che stai utilizzando aggiunge più tempo di quanto risparmi eliminando ulteriori cambiamenti di contesto. In generale, un LIMIT
tra 100 e 1000 è probabile che sia il punto debole.
Naturalmente, in questo esempio, sarebbe ancora più efficiente eliminare tutti i cambiamenti di contesto e fare tutto in un'unica istruzione SQL
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
Avrebbe senso ricorrere a PL/SQL in primo luogo solo se stai eseguendo una sorta di manipolazione dei dati dalla tabella di origine che non puoi ragionevolmente implementare in SQL.
Inoltre, ho usato intenzionalmente un cursore esplicito nel mio esempio. Se utilizzi cursori impliciti, nelle versioni recenti di Oracle ottieni i vantaggi di un BULK COLLECT
con un LIMIT
di 100 implicitamente. C'è un'altra domanda StackOverflow che discute i vantaggi in termini di prestazioni relative dei cursori impliciti ed espliciti con operazioni in blocco che approfondiscono maggiormente quelle particolari rughe.