sql >> Base de Datos >  >> RDS >> Oracle

Oracle:rendimiento de recopilación masiva

Dentro de Oracle, hay una máquina virtual SQL (VM) y una VM PL/SQL. Cuando necesita pasar de una VM a otra, incurre en el costo de un cambio de contexto. Individualmente, esos cambios de contexto son relativamente rápidos, pero cuando realiza un procesamiento fila por fila, pueden sumarse para representar una fracción significativa del tiempo que dedica su código. Cuando usa enlaces masivos, mueve varias filas de datos de una máquina virtual a otra con un solo cambio de contexto, lo que reduce significativamente la cantidad de cambios de contexto y hace que su código sea más rápido.

Tomemos, 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;

luego, cada vez que ejecuto la búsqueda, estoy

  • Realización de un cambio de contexto de la máquina virtual PL/SQL a la máquina virtual SQL
  • Pedirle a la máquina virtual SQL que ejecute el cursor para generar la siguiente fila de datos
  • Realizar otro cambio de contexto de la máquina virtual SQL a la máquina virtual PL/SQL para devolver mi única 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 de la máquina virtual PL/SQL a la máquina virtual SQL, y le pido a SQL que ejecute INSERT y luego incurrir en el costo de otro cambio de contexto de regreso a PL/SQL.

Si source_table tiene 1 millón de filas, eso es 4 millones de cambios de contexto que probablemente representen una fracción razonable del tiempo transcurrido de mi código. Si por el contrario hago un BULK COLLECT con un LIMIT de 100, puedo eliminar el 99 % de mis cambios de contexto al recuperar 100 filas de datos de la máquina virtual SQL en una colección en PL/SQL cada vez que incurro en el costo de un cambio de contexto e insertar 100 filas en la tabla de destino cada vez que incurrir en un cambio de contexto allí.

Si puedo reescribir mi código para hacer uso de 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 la búsqueda, recupero 100 filas de datos en mi colección con un solo conjunto de cambios de contexto. Y cada vez que hago mi FORALL insertar, 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 llega a la ley de los rendimientos decrecientes. Si usaste un LIMIT de 1000 en lugar de 100, eliminaría el 99,9 % de los cambios de contexto en lugar del 99 %. Sin embargo, eso significaría que su colección estaba usando 10 veces más memoria PGA. Y solo eliminaría un 0,18% más de tiempo transcurrido en nuestro ejemplo hipotético. Rápidamente llega a un punto en el que la memoria adicional que está utilizando agrega más tiempo del que ahorra al eliminar los 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 aún más eficiente eliminar todos los cambios de contexto y hacer todo en una sola instrucción SQL

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

En primer lugar, solo tendría sentido recurrir a PL/SQL si está realizando algún tipo de manipulación de los datos de la tabla de origen que no puede implementar razonablemente en SQL.

Además, utilicé intencionalmente un cursor explícito en mi ejemplo. Si está utilizando cursores implícitos, en versiones recientes de Oracle, obtiene los beneficios de una BULK COLLECT con un LIMIT de 100 implícitamente. Hay otra pregunta de StackOverflow que analiza los beneficios de rendimiento relativo de los cursores implícitos y explícitos con operaciones masivas que brinda más detalles sobre esas arrugas particulares.