2011-11-10 14 views
15

En el trabajo, tengo una gran tabla (unas 3 millones de filas, como 40-50 columnas). A veces necesito vaciar algunas de las columnas y llenarlas con nuevos datos. Lo que no esperaba es queactualización x establecer y = null lleva mucho tiempo

UPDATE table1 SET y = null 

toma mucho más tiempo que rellenar la columna con los datos que se genera, por ejemplo, en la consulta SQL de otras columnas de la misma tabla o consultarse desde otras tablas en una subconsulta . No importa si reviso todas las filas de la tabla a la vez (como en la consulta de actualización anterior) o si uso un cursor para recorrer la tabla fila por fila (usando el pk). No importa si utilizo la tabla grande en el trabajo o si creo una pequeña tabla de prueba y la llevo con cientos de millares de filas de prueba. Establecer la columna como nulo siempre lleva más tiempo (a lo largo de las pruebas, encontré factores de 2 a 10) que actualizar la columna con algunos datos dinámicos (que es diferente para cada fila).

¿Cuál es la razón de esto? ¿Qué hace Oracle cuando configura una columna como nula? O bien, ¿cuál es mi error de razonamiento?

Gracias por su ayuda!

P.S .: Estoy usando oracle 11g2, y encontré estos resultados utilizando el desarrollador plsql y el desarrollador de Oracle sql.

+0

¿Puede publicar su plan de ejecución/explicación? – diagonalbatman

+0

¿Hay una cláusula 'where'? – Johan

+0

Si reviso toda la tabla de una vez, no hay cláusula where. Si reviso la tabla fila por fila, aparece una cláusula where que hace referencia a la clave principal de la tabla. El resultado permanece igual en ambas versiones. En cuanto al plan de ejecución, prepararé uno y un ejemplo paso a paso para reproducir el resultado el día de hoy. –

Respuesta

4

Resumen

creo que la actualización a nula es más lento debido a Oracle (incorrectamente) trata de aprovechar la manera en que almacena los nulos, haciendo que con frecuencia reorganizar las filas en el bloque ("bloque del montón compress "), creando una gran cantidad de UNDO y REDO extra.

¿Qué tiene de especial la nulidad?

Desde el Oracle Database Concepts:

"valores nulos se almacenan en la base de datos si caen entre las columnas con valores de datos en estos casos que requieren de 1 byte para almacenar la longitud de la columna (cero)

..

Los nulos finales en una fila no requieren almacenamiento porque un nuevo encabezado de fila indica que las columnas restantes en la fila anterior son nulas. Por ejemplo, si las últimas tres columnas de una tabla son nulas, no se almacena información para esas columnas. con muchas columnas, , las columnas con más probabilidades de contener nulos deberían definirse al final para conservar el espacio en disco ".

prueba

actualizaciones de la evaluación comparativa es muy difícil, porque el verdadero costo de una actualización no se puede medir sólo de la instrucción de actualización. Por ejemplo, los conmutadores de registro no suceden con cada actualización, y la eliminación retardada de bloques ocurrirá más tarde. Para probar con precisión una actualización, debe haber varias ejecuciones, los objetos deben recrearse para cada ejecución, y los valores altos y bajos deben descartarse.

Para simplificar, el siguiente script no arroja resultados altos y bajos, y solo prueba una tabla con una sola columna. Pero el problema aún ocurre independientemente del número de columnas, sus datos y qué columna se actualiza.

Utilicé la utilidad RunStats de http://www.oracle-developer.net/utilities.php para comparar el consumo de recursos de actualización-a-un-valor con la actualización-a-un-nulo.

create table test1(col1 number); 

BEGIN 
    dbms_output.enable(1000000); 

    runstats_pkg.rs_start; 

    for i in 1 .. 10 loop 
     execute immediate 'drop table test1 purge'; 
     execute immediate 'create table test1 (col1 number)'; 
     execute immediate 'insert /*+ append */ into test1 select 1 col1 
      from dual connect by level <= 100000'; 
     commit; 
     execute immediate 'update test1 set col1 = 1'; 
     commit; 
    end loop; 

    runstats_pkg.rs_pause; 
    runstats_pkg.rs_resume; 

    for i in 1 .. 10 loop 
     execute immediate 'drop table test1 purge'; 
     execute immediate 'create table test1 (col1 number)'; 
     execute immediate 'insert /*+ append */ into test1 select 1 col1 
      from dual connect by level <= 100000'; 
     commit; 
     execute immediate 'update test1 set col1 = null'; 
     commit; 
    end loop; 

    runstats_pkg.rs_stop(); 
END; 
/

Resultado

Hay docenas de diferencias, estos son los cuatro Creo que son los más relevantes:

Type Name         Run1   Run2   Diff 
----- ---------------------------- ------------ ------------ ------------ 
TIMER elapsed time (hsecs)    1,269  4,738  3,469 
STAT heap block compress      1  2,028  2,027 
STAT undo change vector size  55,855,008 181,387,456 125,532,448 
STAT redo size      133,260,596 581,641,084 448,380,488 

Soluciones?

La única solución posible que se me ocurre es habilitar la compresión de tablas. El truco de almacenamiento nulo al final no ocurre para las tablas comprimidas. Por lo tanto, aunque el número de "comprimir bloques de bloques" es aún mayor para Run2, de 2028 a 23208, creo que en realidad no hace nada. El tiempo de rehacer, deshacer y transcurrido entre las dos ejecuciones es casi idéntico a la compresión de tabla habilitada.

Sin embargo, existen muchas desventajas potenciales en la compresión de tablas. La actualización a un nulo se ejecutará mucho más rápido, pero cada otra actualización se ejecutará al menos un poco más lento.

1

Eso es porque borra desde los bloques que los datos.

Y delete es la operación más difícil. Si puede evitar un delete, hágalo.

Le recomiendo que cree otra tabla con esa columna nula (Create table as select por ejemplo, o insert select), y llénela (la columna) con su procedimiento. Elimine la tabla anterior y luego cambie el nombre de la nueva tabla con el nombre actual.

ACTUALIZACIÓN:

Otra cosa importante es que usted debe actualizar la columna como es, con nuevos valores. Es inútil establecerlos como nulos y luego volver a llenarlos. Si usted no tiene los valores para todas las filas, se puede hacer la actualización de la siguiente manera:

udpate table1 
set y = (select new_value from source where source.key = table1.key) 

y se establecerá en NULL aquellas filas que no existe en la fuente.

6

¿Está indexada la columna Y? Podría ser que establecer la columna en nulo significa que Oracle tiene que eliminar del índice, en lugar de simplemente actualizarlo. Si ese es el caso, puede soltarlo y volver a generarlo después de actualizar los datos.

EDIT:

se trata sólo de la columna Y que exhibe el tema, o es independiente de la columna que se actualizan

? ¿Puedes publicar la definición de la tabla, incluidas las restricciones?

+0

+1 - Creo que este es el problema. –

+0

Lo siento, se olvidó de decir, no hay índice en esa columna. –

-3

lo que también puede ayudar a acelerar las actualizaciones es usar alter table table1 nologging para que la actualización no genere registros de rehacer. Otra posibilidad es soltar la columna y volver a agregarla. ya que esta es una operación DDL no generará rehacer ni deshacer.

+0

drop column hará lo mismo. Eliminar datos de bloques. En términos de rendimiento, es lo mismo: la misma duración. –

+0

true florin, pasé por alto eso. la sugerencia 'nologging' sigue siendo válida. gracias por corregir! –

+0

sí, la nologging acelerará la consulta. –

-1

Me gustaría probar lo que Tom Kyte sugirió en las actualizaciones de gran tamaño. Cuando se trata de tablas enormes, es mejor que haga lo siguiente: tome algunas filas, actualícelas, tome algunas más, actualícelas, etc. No intente publicar una actualización en toda la tabla. Es un movimiento asesino desde el principio.

Básicamente cree la tabla indexada binary_integer, busque 10 filas a la vez y actualícelas.

Aquí hay una pieza de código que he utilizado de tablas grandes con éxito. Porque soy flojo y es como 2AM ahora enfermo solo copie y pegue aquí y déjeme averiguarlo, hágamelo saber si necesita ayuda:

DECLARE 

    TYPE BookingRecord IS RECORD ( 
     bprice number, 
     bevent_id number, 
     book_id number 
    ); 

    TYPE array is TABLE of BookingRecord index by binary_integer; 
    l_data array; 

CURSOR c1 is 
    SELECT LVC_USD_PRICE_V2(ev.activity_version_id,ev.course_start_date,t.local_update_date,ev.currency,nvl(t.delegate_country,ev.sponsor_org_country),ev.price,ev.currency,t.ota_status,ev.location_type) x, 
     ev.title, 
     t.ota_booking_id 
     FROM [email protected] t, 
      [email protected] ev 
     WHERE t.event_id = ev.event_id 
     and t.ota_booking_id = 
BEGIN 
    open c1; 
     loop 
      fetch c1 bulk collect into l_data limit 20; 

      for i in 1..l_data.count 
       loop 
        update ou_inc_int_t_01 
         set price = l_data(i).bprice, 
          updated = 'Y' 
        where booking_id = l_data(i).book_id; 
       end loop; 

      exit when c1%notfound; 
     end loop; 
     close c1; 
END; 
+1

Estoy totalmente en desacuerdo con que sea mejor reemplazar una gran declaración SQL con muchas pequeñas sentencias SQL. (Aunque en un entorno multiusuario en ocasiones puede necesitar hacer esto para concurrencia, o debido a recursos limitados, como un pequeño espacio de tabla UNDO). Varias pequeñas sentencias de SQL requieren tiempo para alternar entre SQL y PL/SQL. Y una ACTUALIZACIÓN única puede requerir mucho menos deshacer que múltiples ACTUALIZACIONES. (Un FORALL en lugar de un FOR ayudaría con el cambio de contexto, pero no parece reducir el tamaño de UNDO). –

Cuestiones relacionadas