2012-07-12 44 views
7

¿Me puede ayudar a entender esta frase?Oracle: rendimiento de Bulk Collect

Sin la unen a granel, PL/SQL envía una instrucción SQL para el motor de SQL para cada registro que se inserta, actualiza o suprime conduce a cambios de contexto que perjudican el rendimiento.

Respuesta

17

dentro de Oracle, hay una máquina de SQL virtual (VM) y PL/SQL VM. Cuando necesita pasar de una máquina virtual a la otra máquina virtual, incurre en el costo de un cambio de contexto. Individualmente, esos cambios de contexto son relativamente rápidos, pero cuando se está procesando fila por fila, pueden sumarse a la cuenta durante una fracción significativa del tiempo que su código está gastando. Cuando utiliza enlaces masivos, mueve múltiples filas de datos de una máquina virtual a la otra con un único cambio de contexto, lo que reduce significativamente el número de cambios de contexto y hace que su código sea más rápido.

Tome, por ejemplo, un cursor explícito. Si escribo algo como esto

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; 

continuación, cada vez que ejecute la búsqueda, soy

  • Realizar un cambio de contexto desde el PL/SQL VM al SQL VM
  • Pedir la máquina virtual SQL para ejecutar el cursor para generar la siguiente fila de datos
  • escénicas otro cambio de contexto desde la máquina virtual de SQL de nuevo a la PL/SQL VM para volver a mi sola fila de datos

Y cada vez que inserto una fila, hago lo mismo. Estoy incurriendo en el costo de un cambio de contexto para enviar una fila de datos desde la máquina virtual PL/SQL a la máquina virtual SQL, pidiendo al SQL que ejecute la instrucción INSERT, y luego incurra en el costo de otro cambio de contexto a PL/SQL.

Si source_table tiene 1 millón de filas, son 4 millones de cambios de contexto que probablemente representarán una fracción razonable del tiempo transcurrido de mi código. Si, por otro lado, hago un BULK COLLECT con un LIMIT de 100, puedo eliminar el 99% de mis cambios de contexto recuperando 100 filas de datos de la VM de SQL en una colección en PL/SQL cada vez que incurro en el costo de un cambio de contexto e insertando 100 filas en la tabla de destino cada vez que incurro en un cambio de contexto allí.

Si puede reescribir mi código para hacer uso de las operaciones masivas

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; 

Ahora, cada vez que ejecuto el buscar, recupero 100 filas de datos en mi colección con un único conjunto de cambios de contexto. Y cada vez que hago mi inserción FORALL, estoy insertando 100 filas con un solo conjunto de cambios de contexto. Si source_table tiene 1 millón de filas, esto significa que he pasado de 4 millones de cambios de contexto a 40,000 cambios de contexto. Si los cambios de contexto representaron, digamos, el 20% del tiempo transcurrido de mi código, eliminé el 19.8% del tiempo transcurrido.

Puede aumentar el tamaño del LIMIT para reducir aún más el número de cambios de contexto pero rápidamente se topa con la ley de rendimientos decrecientes. Si usó un LIMIT de 1000 en lugar de 100, eliminaría el 99.9% de los cambios de contexto en lugar del 99%. Eso significaría que su colección estaba usando 10 veces más memoria PGA, sin embargo. Y solo eliminaría un 0,18% más de tiempo transcurrido en nuestro ejemplo hipotético. Llega muy rápidamente a un punto en el que la memoria adicional que está utilizando le agrega más tiempo del que ahorra al eliminar cambios de contexto adicionales. En general, un LIMIT en algún lugar entre 100 y 1000 es probable que sea el punto ideal.

Por supuesto, en este ejemplo, sería más eficiente todavía para eliminar todos los cambios de contexto y hacer todo en una única sentencia de SQL

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

Sólo tendría sentido que recurrir a PL/SQL en el primer lugar si está haciendo algún tipo de manipulación de los datos de la tabla fuente que no puede implementar razonablemente en SQL.

Además, usé un cursor explícito en mi ejemplo intencionalmente. Si está utilizando cursores implícitos, en versiones recientes de Oracle, obtiene los beneficios de un BULK COLLECT con un LIMIT de 100 implícitamente. Hay otra pregunta de StackOverflow que analiza el relativo performance benefits of implicit and explicit cursors with bulk operations que entra en más detalles sobre esas arrugas particulares.

1

AS Entiendo esto, hay dos motores involucrados, PL/SQL engine and SQL Engine. La ejecución de una consulta que hacer uso de un motor en un momento es más eficiente que la conmutación entre los dos

Ejemplo:

INSERT INTO t VALUES(1) 

es procesada por motor SQL mientras

FOR Lcntr IN 1..20 

    END LOOP 

es ejecutado por PL/Motor SQL

Si combina las dos declaraciones anteriores, inserte INSERT en el ciclo,

FOR Lcntr IN 1..20 
    INSERT INTO t VALUES(1) 
END LOOP 

Oracle cambiará entre los dos motores, para cada (20) iteraciones. En este caso INSERT GRANEL se recomienda que hace uso del motor de PL/SQL a lo largo de la ejecución

+0

Su última frase es un poco engañosa. BULK hace que el cambio de contexto ocurra solo una vez, aunque todavía sucede. – viper

Cuestiones relacionadas