Sei sulla pagina 1di 3

Clusula BULK COLLECT para mejorar el rendimiento al realizar

procesamiento masivo

Yo siempre he dicho que cuando para hacer algo se pueden utilizar sentencias SQL
sencillas, no resulta conveniente emplear complicados procedimientos PL/SQL que
implementen la misma solucin. Sin embargo, hay situaciones en que para mejorar el
rendimiento de determinados bucles FOR en los que se realizan actualizaciones masivas
sobre una determinada tabla de la base de datos Oracle, resulta conveniente utilizar
tcnicas PLSQL de procesamiento masivo (lo que en ingls se denomina BULK
COLLECT).

Para entender mejor en qu consiste esta tcnica, primero hay que comprender los motivos
por los que un simple bucle FOR puede generar importantes problemas de rendimiento.
Veamos el siguiente cdigo PL/SQL:
FOR selrec IN
(SELECT * FROM tabla_enorme
ORDER BY muchas columnas)
LOOP
-- Gran cantidad de cdigo que omito y al final:
UPDATE tabla_enorme SET ...
WHERE clave_primaria = selrec.clave_primaria;
COMMIT;
END LOOP;

El presente cdigo es un extracto de un cdigo PLSQL que uno de los lectores de este blog
me envi por correo electrnico indicndome que presentaba graves problemas de
rendimiento, cosa que desde un primer momento a mi no me extra, considerando que la
tabla tabla_enorme contena ms de 30 millones de registros. Es el tpico ejemplo de
cdigo PL/SQL que para mejorar su rendimiento necesita que se aplique la tcnica o
funcionalidad de BULK COLLECT (que traducido directamente a castellano sera similar a
decir "recogida a granel" pero que aqu traduciremos por procesamiento masivo).

Antes de profundizar en el tema, debemos considerar que si leemos los datos de una base
de datos Oracle sin necesidad de enviar datos de vuelta a la misma base de datos, no es
necesario aplicar la tcnica del BULK COLLECT. Es decir, el siguiente cdigo PL/SQL es
perfectamente utilizable y no debera generar problemas de rendimiento:
FOR x IN
(SELECT * FROM tabla_enorme t WHERE ...)
LOOP
DBMS_OUTPUT.PUT_LINE (x.col1||...||x.colN);
END LOOP;

En el ejemplo vemos que estamos leyendo los datos utilizando un SELECT, pero que, aun
tratndose de una operacin registro a registro, no se utilizan para ser retornados a la base
de datos utilizando un INSERT, UPDATE o DELETE. La sentencia SQL SELECT ya
utiliza, de por s, la funcionalidad de procesamiento masivo, ya que, desde la versin 10g
de la base de datos Oracle, el cdigo "FOR x IN (SELECT ...)", aunque no sea visible para
el desarrollador, ha sido optimizado para almacenar internamente en matrices de 100
registros los resultados que devuelve dicho SELECT.

Sin embargo, veamos que ocurre con este otro ejemplo de cdigo PL/SQL:
FOR x IN
(SELECT clave_primaria, col1, col2
FROM tabla_enorme)
LOOP
x.col1 := calculos(x.col1, x.col2);
UPDATE tabla_enorme
SET col1 = x.col1
WHERE clave_primaria = x.clave_primaria;
COMMIT;
END LOOP;

En este cdigo PLSQL, la sentencia SELECT estar utilizando la funcionalidad de


procesamiento masivo incorporada con la versin 10g, pero no ocurrir lo mismo con el
procesamiento de la sentencia UPDATE. La pregunta ahora es, cmo podemos mejorar el
rendimiento de un cdigo tan sencillo? El primer cambio que realizaremos es algo bastante
sencillo:
FOR x IN
(SELECT rowid, col1, col2
FROM tabla_enorme)
LOOP
x.col1 := calculos(x.col1, x.col2);
UPDATE tabla_enorme
SET col1 = x.col1
WHERE rowid = x.rowid;
END LOOP;

El nuevo cdigo lee cada registro de la tabla_enorme, realiza algn tipo de procesado de
los datos y, finalmente, realiza el UPDATE de un campo de la misma tabla por
ROWID. Qu hemos conseguido con respecto al primer cdigo?, habremos evitado
realizar 30 millones de veces un UNIQUE SCAN sobre el ndice de la clave primaria, ya
que estaremos accediendo a la tabla por ROWID. Acceder a la tabla a travs del ndice de la
clave primaria puede implicar de tres a cinco operaciones de entrada/salida (I/O) por
iteracin, por lo que acceder a la tabla por ROWID en una tabla tan grande nos puede
ahorrar ms de cien millones de operaciones de entrada/salida.

Otro cambio que observaris es que hemos eliminado el COMMIT despus de cada
iteracin. Aparte de que realizar un COMMIT despus de que cada registro es procesado
resultar lento y reducir bastante el rendimiento, si en mitad del procesamiento se produce
algn error, habremos dejado la base de datos Oracle en un estado bastante inconsistente,
por no decir corrupto (con media tabla_enorme actualizada y la otra sin actualizar).

Pero esto que hemos hecho no tiene en realidad nada que ver con la tcnica o funcionalidad
de procesamiento masivo. Veamos el siguiente cdigo PL/SQL en el que hemos, por fin,
utilizado un FETCH con la clusula BULK COLLECT:
CREATE OR REPLACE PROCEDURE procesamiento_masivo
AS
TYPE matriz_rowid IS TABLE OF ROWID;
TYPE matriz_col1 IS TABLE OF tabla_enorme.col1%TYPE;
TYPE matriz_col2 IS TABLE OF tabla_enorme.col2%TYPE;

CURSOR cur IS SELECT rowid, col1, col2


FROM tabla_enorme;
m_rowid matriz_rowid;
m_col1 matriz_col1;
m_col2 matriz_col2;
contador NUMBER := 100;

BEGIN
OPEN cur;
LOOP
FETCH cur BULK COLLECT
INTO m_rowid, m_col1, m_col2 LIMIT contador;
FOR i IN 1 .. m_rowid.count
LOOP
m_col1(i) := calculos(m_col1(i), m_col2(i));
END LOOP;
FORALL i IN 1 .. m_rowid.count
UPDATE tabla_enorme
SET col1 = m_col1(i)
WHERE rowid = m_rowid(i);
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE cur;
END;

El nuevo cdigo utiliza sin ningn tipo de restriccin la funcionalidad de procesamiento


masivo BULK COLLECT, de manera que los registros se procesan de cien en cien (valor
que podremos cambiar con slo asignar un valor diferente a la variable contador). Y, una
vez procesados, lo que hacemos es un UPDATE masivo (bulk update) utilizando la
sentencia PL/SQL FORALL.

Es fcil observar que el cdigo PLSQL que utiliza la funcionalidad BULK COLLECT es
bastante ms complicado y mucho menos intuitivo que el original, pero los resultados a
nivel de rendimiento van a ser realmente sorprendentes. Para tablas con 30 millones de
registros, utilizar la funcionalidad de procesamiento masivo puede hacer que nuestro cdigo
se ejecute entre diez y veinte veces ms rpido que sin utilizar dicha funcionalidad (ojo que,
a veces, la mejora de rendimiento puede ser incluso hasta superior).

Potrebbero piacerti anche