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.
Su última frase es un poco engañosa. BULK hace que el cambio de contexto ocurra solo una vez, aunque todavía sucede. – viper