2010-04-14 21 views
28

Tengo una tabla con muchos registros (podría ser más de 500 000 o 1 000 000). Agregué una nueva columna en esta tabla y necesito completar un valor para cada fila en la columna, usando el valor de fila correspondiente de otra columna en esta tabla.Manera eficiente de actualizar todas las filas en una tabla

Traté de usar transacciones separadas para seleccionar cada fragmento siguiente de 100 registros y actualizar el valor para ellos, pero todavía lleva horas actualizar todos los registros en Oracle10, por ejemplo.

¿Cuál es la forma más eficiente de hacer esto en SQL, sin usar algunas características específicas del dialecto, por lo que funciona en todas partes (Oracle, MSSQL, MySQL, PostGre etc.)?

INFORMACIÓN ADICIONAL: No hay campos calculados. Hay índices. Se utilizan sentencias SQL generadas que actualizan la tabla fila por fila.

+0

Pocas personas GOTO, el extremo de disabiling 'es INDEX' si está presente en la columna actualizada/instered, y dejar que el trabajo de la noche a la analiza . – Guru

+1

Necesitamos más información. Cuéntanos sobre el esquema de la tabla ... ¿cualquier columna "calculada"? Cualquier índice? 500k - 1m filas NO es una gran cantidad de registros de ninguna manera. – Timothy

+0

Gracias a todos por la respuesta rápida. Me salté la parte que estoy usando declaraciones SQL generadas. Ahora investigué profundamente y parece que las actualizaciones SQL generadas ¡fila por fila! Por lo tanto, cualquier intento de separar en fragmentos de 100 registros no tenía sentido ... Cambiaré el código para generar una instrucción SQL UPDATE adecuada, como en la respuesta aceptada. –

Respuesta

42

La forma más habitual es utilizar UPDATE:

UPDATE mytable 
    SET new_column = <expr containing old_column> 

Usted debe ser capaz de hacer esto es una sola transacción.

+0

Parece que el OP sabe cómo hacer esto en una sola transacción, pero hay un problema de rendimiento, por lo que intentó agruparlo en transacciones separadas. – Timothy

+1

Eso es posible, pero es extraordinario que las filas de 1 M tomen tanto tiempo para actualizar una sola columna. También es posible que el OP esté actualizando un registro a la vez, ya sea por falta de comprensión de las operaciones establecidas, o porque están tratando de calcular el nuevo valor en el código del cliente (ya sea por necesidad o de nuevo, debido a la falta de comprensión) . Cualquiera que sea el caso, podré actualizar mi respuesta si el OP indica cuál de los casos anteriores se aplica a ellos. –

+0

Bastante justo. Estoy de acuerdo en que se necesita más información. – Timothy

2

Puede soltar cualquier índice en la tabla, luego hacer su inserción, y luego volver a crear los índices.

+0

+1. Era cuestión de tiempo sugerir esto, pero sí, para 10M o más filas, puedes hacerlo siempre y cuando lo hagas rápido y rápido. – Guru

+4

Por el amor de los dioses que adoras, haz esto en un momento tranquilo. De lo contrario, tus usuarios te rastrearán, te torturarán, te matarán, te abofetearán, alquitranarán y emplumarán los restos, luego los quemarán y escupirán en tus partes del cuerpo carbonizadas. A lo mínimo. Probablemente lo harán mucho peor. – paxdiablo

+1

Suena como los gemidos de un DBA que ha sido sacrificado en el altar del rendimiento ... – Timothy

0

Puede que no te funcione, pero es una técnica que he usado un par de veces en el pasado para circunstancias similares.

creado updated_ {table_name}, luego seleccione insertar en esta tabla en lotes. Una vez finalizado, esto depende de Oracle (que no conozco ni uso) que soporte la capacidad de cambiar el nombre de las tablas de forma atómica. updated_ {table_name} se convierte en {table_name}, mientras que {table_name} se convierte en original_ {table_name}.

La última vez que tuve que hacer esto fue para una tabla muy indexada con varios millones de filas que definitivamente no pudieron bloquearse durante el tiempo necesario para realizar algunos cambios serios.

8

Como Marcelo sugiere:

UPDATE mytable 
SET new_column = <expr containing old_column>; 

Si esto toma demasiado tiempo y falla debido a "instantáneas demasiado viejos" errores (por ejemplo, si la expresión consulta otra mesa altamente activa), y si el nuevo valor para el la columna es siempre NO NULO, puede actualizar la tabla en lotes:

UPDATE mytable 
SET new_column = <expr containing old_column> 
WHERE new_column IS NULL 
AND ROWNUM <= 100000; 

Sólo tiene que ejecutar esta declaración, COMMIT, a continuación, ejecute de nuevo; enjuague, repita hasta que informe "0 filas actualizadas". Llevará más tiempo, pero es menos probable que falle cada actualización.

EDIT:

Una mejor alternativa que debe ser más eficiente es el uso de la API de DBMS_PARALLEL_EXECUTE.

Código Muestra (a partir de documentos de Oracle):

DECLARE 
    l_sql_stmt VARCHAR2(1000); 
    l_try NUMBER; 
    l_status NUMBER; 
BEGIN 

    -- Create the TASK 
    DBMS_PARALLEL_EXECUTE.CREATE_TASK ('mytask'); 

    -- Chunk the table by ROWID 
    DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_ROWID('mytask', 'HR', 'EMPLOYEES', true, 100); 

    -- Execute the DML in parallel 
    l_sql_stmt := 'update EMPLOYEES e 
     SET e.salary = e.salary + 10 
     WHERE rowid BETWEEN :start_id AND :end_id'; 
    DBMS_PARALLEL_EXECUTE.RUN_TASK('mytask', l_sql_stmt, DBMS_SQL.NATIVE, 
           parallel_level => 10); 

    -- If there is an error, RESUME it for at most 2 times. 
    l_try := 0; 
    l_status := DBMS_PARALLEL_EXECUTE.TASK_STATUS('mytask'); 
    WHILE(l_try < 2 and l_status != DBMS_PARALLEL_EXECUTE.FINISHED) 
    LOOP 
    l_try := l_try + 1; 
    DBMS_PARALLEL_EXECUTE.RESUME_TASK('mytask'); 
    l_status := DBMS_PARALLEL_EXECUTE.TASK_STATUS('mytask'); 
    END LOOP; 

    -- Done with processing; drop the task 
    DBMS_PARALLEL_EXECUTE.DROP_TASK('mytask'); 

END; 
/

Oracle Docs: https://docs.oracle.com/database/121/ARPLS/d_parallel_ex.htm#ARPLS67333

+0

¡Creo que esta es una gran idea para tablas muy grandes y muy utilizadas! Todavía no tuve esos fallos, pero tiene +1 :) –

+1

Algo me dice que esto también evita bloquear toda la tabla durante mucho tiempo. Con esto solo se bloqueará un trozo 'pequeño' durante la duración de una sola ejecución. –

Cuestiones relacionadas