2010-09-14 32 views
30

Necesito inicializar un nuevo campo con el valor -1 en una tabla de registro de 120 millones.La forma más rápida de actualizar 120 millones de registros

Update table 
     set int_field = -1; 

Lo dejé funcionar durante 5 horas antes de cancelarlo.

Intenté ejecutarlo con el nivel de transacción configurado para leer no confirmado con los mismos resultados.

Recovery Model = Simple. 
MS SQL Server 2005 

¿Algún consejo sobre cómo hacer esto más rápido?

+3

El problema está basado en IO. Remus abordó este problema aquí: http://stackoverflow.com/questions/3523831/update-statement-running-for-too-long-or-not/3523903#3523903 –

+0

Me preocupaba que pudiera estar basado en IO. Lamentablemente, no hay mucho que pueda hacer al respecto. ¿Los lotes incrementales más pequeños funcionarán más rápido? –

+0

No. El volumen de datos general será el mismo: páginas actualizadas y páginas de registro. Todavía son 120 millones de filas, y esas filas efectivamente se escriben dos veces (una para el registro y otra para la tienda de respaldo). Entonces, el tiempo debe ser proporcional al tamaño total de su mesa. Si se trata de una tabla con un solo byte de 4 bytes, eso equivale a 1G de datos para escribir (120 * 4 = 480, * 2 - 960). Entonces, ¿qué tan grande es la mesa en general? –

Respuesta

25

La única forma correcta de actualizar una tabla de registros de 120M es con una declaración SELECT que rellena una segunda tabla. Tienes que tener cuidado al hacer esto. Instrucciones a continuación.


simple caso

Para una tabla w/un índice agrupado, durante un tiempo w/salida simultánea LMD:

  • SELECT *, new_col = 1 INTO clone.BaseTable FROM dbo.BaseTable
  • índices recrear restricciones, etc. en la nueva tabla
  • cambiar antiguo y nuevo con ALTER SCHEMA ... TRANSFER.
  • caída de la mesa de edad

Si no puede crear un esquema clon, un nombre de tabla diferente en el mismo esquema va a hacer. Recuerde cambiar el nombre de todas sus restricciones y factores desencadenantes (si corresponde) después del cambio.


Caso no sencilla

En primer lugar, volver a crear su BaseTable con el mismo nombre bajo un esquema diferente, por ejemplo clone.BaseTable. Usar un esquema separado simplificará el proceso de cambio de nombre más adelante.

  • incluir el índice agrupado, en su caso. Recuerde que las claves principales y las restricciones únicas pueden agruparse, pero no necesariamente.
  • Incluya columnas de identidad y columnas calculadas, si corresponde.
  • Incluya su nueva columna INT, donde corresponda.
  • No incluya cualquiera de los siguientes:
    • desencadena
    • restricciones de clave externa
    • índices no agrupados llaves/primaria/restricciones únicas
    • restricciones de comprobación o restricciones predeterminadas. Los valores predeterminados no hacen mucha diferencia, pero estamos tratando de mantener cosas mínimas.

Entonces, prueba de su inserción w/1000 filas:

-- assuming an IDENTITY column in BaseTable 
SET IDENTITY_INSERT clone.BaseTable ON 
GO 
INSERT clone.BaseTable WITH (TABLOCK) (Col1, Col2, Col3) 
SELECT TOP 1000 Col1, Col2, Col3 = -1 
FROM dbo.BaseTable 
GO 
SET IDENTITY_INSERT clone.BaseTable OFF 

examinar los resultados. Si todo parece en orden:

  • truncar la tabla clon
  • asegúrese de que la base de datos en el modelo de cargas masivas de registros o de recuperación simple
  • realizar la inserción completa.

Esto llevará un tiempo, pero no tanto como una actualización. Una vez que se completa, verifique los datos en la tabla de clonación para asegurarse de que todo sea correcto.

A continuación, vuelva a crear todas las claves primarias no agrupadas/restricciones/índices únicos y restricciones de clave externa (en ese orden). Recrear valores predeterminados y verificar restricciones, si corresponde. Recrear todos los factores desencadenantes. Vuelva a crear cada restricción, índice o activador en un lote por separado. por ejemplo:

ALTER TABLE clone.BaseTable ADD CONSTRAINT UQ_BaseTable UNIQUE (Col2) 
GO 
-- next constraint/index/trigger definition here 

Por último, se mueven dbo.BaseTable a un esquema de copia de seguridad y clone.BaseTable al esquema dbo (o donde quiera que su tabla se supone que vive).

-- -- perform first true-up operation here, if necessary 
-- EXEC clone.BaseTable_TrueUp 
-- GO 
-- -- create a backup schema, if necessary 
-- CREATE SCHEMA backup_20100914 
-- GO 
BEGIN TRY 
    BEGIN TRANSACTION 
    ALTER SCHEMA backup_20100914 TRANSFER dbo.BaseTable 
    -- -- perform second true-up operation here, if necessary 
    -- EXEC clone.BaseTable_TrueUp 
    ALTER SCHEMA dbo TRANSFER clone.BaseTable 
    COMMIT TRANSACTION 
END TRY 
BEGIN CATCH 
    SELECT ERROR_MESSAGE() -- add more info here if necessary 
    ROLLBACK TRANSACTION 
END CATCH 
GO 

Si necesita liberar espacio en disco, puede caer la tabla original en este momento, aunque puede ser prudente para mantenerlo en torno a un poco más de tiempo.

No hace falta decir que esto es idealmente una operación sin conexión. Si tiene personas que modifican datos mientras realiza esta operación, deberá realizar una operación de actualización con el cambio de esquema. Recomiendo crear un disparador en dbo.BaseTable para registrar todos los DML en una tabla separada. Habilite este disparador antes de comenzar la inserción. Luego, en la misma transacción en la que realiza la transferencia de esquema, use la tabla de registro para realizar un true-up. ¡Pruebe esto primero en un subconjunto de datos! Los deltas son fáciles de arruinar.

3

Si su int_field está indexado, elimine el índice antes de ejecutar la actualización. A continuación, cree su índice de nuevo ...

5 horas parecen mucho para 120 millones de visitas.

+4

Lo que gana al aplicar el cambio de datos, lo perderá (si no empeora) al volver a aplicar el índice. –

+1

y el campo no está indexado. –

2

Lo que probaría primero es
para eliminar todas las restricciones, índices, desencadenantes e índices de texto completo antes de actualizar.

Si lo anterior no fue lo suficientemente eficiente, mi siguiente movimiento sería
para crear un archivo CSV con 12 millones de registros e importarlo en bloque usando bcp.

Por último, crearía una nueva tabla de montón (es decir, una tabla sin clave principal) sin índices en un grupo de archivos diferente, rellenarlo con -1. Particiona la tabla anterior y agrega la nueva partición con "cambiar".

+0

¿Cómo es que no mencionaste los desencadenadores incapacitantes? –

+0

Gracias, Denis. También mencioné deshabilitar indexación FTS – Sung

1

Suena como un problema de indexación, como se menciona en Pabla Santa Cruz. Como su actualización no es condicional, puede DEJAR caer la columna y RE-ADD con un valor DEFAULT.

+1

Tal vez estoy haciendo algo mal, pero mi experiencia con los valores predeterminados es que no se configuran cuando se crea una columna, sino solo cuando se inserta un registro. –

+0

Probablemente tengas razón (nunca he hecho esto). Quizás si no permites NULLS en la columna, obtendría el valor de la restricción de valor predeterminada (ya que se agrega en línea, tendría un nombre generado automáticamente). – Brad

+0

FYI: eso no funciona. Lo intenté. Una buena idea, sin embargo. –

8

Rompo la tarea en unidades más pequeñas. Pruebe con diferentes intervalos de tamaño de lote para su tabla, hasta que encuentre un intervalo que funcione de manera óptima. Aquí hay una muestra que he usado en el pasado.

declare @counter int 
declare @numOfRecords int 
declare @batchsize int 

set @numOfRecords = (SELECT COUNT(*) AS NumberOfRecords FROM <TABLE> with(nolock)) 
set @counter = 0 
set @batchsize = 2500 

set rowcount @batchsize 
while @counter < (@numOfRecords/@batchsize) +1 
begin 
set @counter = @counter + 1 
Update table set int_field = -1 where int_field <> -1; 
end 
set rowcount 0 
0

En general, la recomendación están al lado:

  1. Quitar o deshabilitar todos los índices, activadores, restricciones de la tabla;
  2. Realice COMMIT con más frecuencia (por ejemplo, después de cada 1000 registros que se actualizaron);
  3. Use seleccione ... en.

Pero en el caso particular, debe elegir la solución más adecuada o su combinación.

También tenga en cuenta que en algún momento el índice podría ser útil, p. cuando realiza la actualización de la columna no indexada por alguna condición.

2
set rowcount 1000000 
Update table set int_field = -1 where int_field<>-1 

ver la rapidez con que se lleva, ajuste y repetir según sea necesario

+12

'set rowcount' está en desuso. Use 'update top (N)' en su lugar. –

12

Si usted tiene el espacio en disco, puede utilizar SELECT INTO y crear una nueva tabla.Se registra mínimamente, por lo que iría mucho más rápido

select t.*, int_field = CAST(-1 as int) 
into mytable_new 
from mytable t 

-- create your indexes and constraints 

GO 

exec sp_rename mytable, mytable_old 
exec sp_rename mytable_new, mytable 

drop table mytable_old 
+0

Sí, sí sí. La actualización es * muy * logarítmica. –

+0

@MikeForman Hola, ¿puedes decirme cómo harías para habilitar la nologificación en la solución anterior –

0

Si la tabla tiene un índice que se puede iterar sobre pondría update top(10000) declaración en un bucle mientras se mueve a través de los datos. Eso mantendría el registro de transacciones delgado y no tendrá un impacto tan grande en el sistema de disco. Además, recomendaría jugar con la opción maxdop (configurándola más cerca de 1).

2

Cuando añadiendo una nueva columna ("inicializar un nuevo campo") y el establecimiento de un único valor para cada fila existente, utilizo la siguiente táctica:

ALTER TABLE MyTable 
add NewColumn int not null 
    constraint MyTable_TemporaryDefault 
    default -1 

ALTER TABLE MyTable 
drop constraint MyTable_TemporaryDefault 

Si la columna es anulable y Don 't incluye una restricción "declarada", la columna se establecerá en nulo para todas las filas.

+0

? ¿Sabes si tiene el perfil de rendimiento de una instrucción 'UPDATE', una declaración' INSERT' grabada en lote, o alguna otra cosa ? –

+0

Sospecho "algo más". Nunca he hecho esto en mesas verdaderamente grandes, pero tiende a ejecutarse * muy * rápidamente en las moderadas. De todos modos, cuando tiene que agregar una columna a una tabla existente, * tiene * que pagar un precio de "todo o nada", ya que no puede agregarlo a un solo lote de filas a la vez. (Lado invertido es INSERTAR .... SELECCIONAR ... para N filas a la vez, pero si lo hace probablemente deba programar un tiempo de inactividad, momento en el que no se debe bloquear, bloquear ni sincronizar tan crítico.) –

+0

He intentado esto en una tabla con> 380 millones de filas y parece que está actualizando la tabla de la misma manera y tomando una cantidad de tiempo similar. – Otake

0
declare @cnt bigint 
set @cnt = 1 

while @cnt*100<10000000 
begin 

UPDATE top(100) [Imp].[dbo].[tablename] 
    SET [col1] = xxxx  
WHERE[col2] is null 

    print '@cnt: '+convert(varchar,@cnt) 
    set @[email protected]+1 
    end 
Cuestiones relacionadas