2012-07-12 8 views
7

Puoi aiutarmi a capire questa frase?Oracle: raccolta collettiva performance

Senza la legano alla rinfusa, PL/SQL invia un'istruzione SQL per il motore SQL per ogni record che viene inserito, aggiornato o eliminato portando a cambi di contesto che fanno male le prestazioni.

risposta

17

In Oracle, vi è una macchina virtuale SQL (VM) e una VM PL/SQL. Quando è necessario passare da una VM all'altra VM, si incorre nel costo di un cambio di contesto. Individualmente, questi cambiamenti di contesto sono relativamente rapidi, ma quando si esegue l'elaborazione riga per riga, possono sommarsi per tenere conto di una frazione significativa del tempo trascorso dal codice. Quando si utilizzano i binding collettivi, si spostano più righe di dati da una VM all'altra con un singolo spostamento di contesto, riducendo significativamente il numero di cambiamenti di contesto, rendendo più veloce il codice.

Prendere, ad esempio, un cursore esplicito. Se scrivo qualcosa di simile

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; 

poi ogni volta che esegue l'operazione di recupero, sono

  • Esecuzione di uno spostamento contesto del PL/SQL VM a SQL VM
  • Chiedendo la VM SQL per eseguire il cursore per generare la riga successiva di dati
  • Esecuzione di un altro spostamento di contesto dalla VM SQL al PL/SQL VM 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 l'istruzione INSERT e quindi di sostenere il costo di un altro spostamento di contesto in PL/SQL.

Se source_table ha 1 milione di righe, si tratta di 4 milioni di cambiamenti di contesto che probabilmente rappresentano una frazione ragionevole del tempo trascorso del mio codice. Se, d'altra parte, eseguo uno BULK COLLECT con un LIMIT su 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 incorro nel costo di un cambio di contesto e l'inserimento di 100 righe nella tabella di destinazione ogni volta che si verifica un cambio di contesto.

Se può riscrivere il mio codice di fare uso di 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 l'operazione di recupero, posso recuperare 100 righe di dati nella mia collezione con un unico insieme di contesto turni. E ogni volta che inserisco il mio inserto FORALL, inserisco 100 righe con un singolo set di shift di contesto. Se source_table ha 1 milione di righe, ciò significa che sono passati da 4 milioni di cambiamenti di contesto a 40.000 cambiamenti di contesto. Se il contesto cambia, per esempio, il 20% del tempo trascorso del mio codice, ho eliminato il 19,8% del tempo trascorso.

È possibile aumentare la dimensione dello LIMIT per ridurre ulteriormente il numero di cambiamenti di contesto ma si colpisce rapidamente la legge dei rendimenti decrescenti. Se hai utilizzato uno LIMIT di 1000 anziché 100, devi eliminare il 99,9% dei cambiamenti di contesto anziché il 99%. Ciò significherebbe che la tua raccolta utilizzava 10 volte più memoria PGA, comunque. E eliminerebbe solo lo 0,18% in più di tempo trascorso nel nostro esempio ipotetico. Raggiungi molto rapidamente un punto in cui la memoria aggiuntiva che stai utilizzando aggiunge più tempo del tuo, eliminando ulteriori cambiamenti di contesto. In generale, uno LIMIT da qualche parte tra 100 e 1000 è probabile che sia il punto debole.

Naturalmente, in questo esempio, sarebbe più efficace ancora per eliminare tutti i turni di contesto e fare tutto in una singola istruzione SQL

INSERT INTO dest_table(col1, col2, ... , colN) 
    SELECT col1, col2, ... , colN 
    FROM source_table; 

Sarebbe senso solo ricorrere a PL/SQL nella in primo luogo se stai facendo una sorta di manipolazione dei dati dalla tabella di origine che non puoi implementare ragionevolmente in SQL.

Inoltre, ho utilizzato intenzionalmente un cursore esplicito nel mio esempio. Se si utilizzano cursori impliciti, nelle versioni recenti di Oracle, si ottengono implicitamente i vantaggi di uno BULK COLLECT con un valore LIMIT di 100. C'è un'altra domanda StackOverflow che discute il relativo performance benefits of implicit and explicit cursors with bulk operations che va più nel dettaglio su quelle rughe particolari.

1

AS Capisco questo, ci sono due motori coinvolti, PL/SQL engine and SQL Engine. Esecuzione di una query che fanno uso di un motore alla volta è più efficiente di commutazione tra i due

Esempio:

INSERT INTO t VALUES(1) 

viene processato da motore SQL mentre

FOR Lcntr IN 1..20 

    END LOOP 

viene eseguito da PL/Motore SQL

Se si combinano le due istruzioni precedenti, inserendo INSERT nel ciclo,

Oracle passa tra i due motori, per ciascuna (20) iterazioni. In questo caso si consiglia BULK INSERT che utilizza il motore PL/SQL per tutta l'esecuzione

+0

L'ultima frase è un po 'ingannevole. BULK fa in modo che l'interruttore di contesto avvenga solo una volta, anche se succede ancora. – viper