2009-05-14 16 views
8

Estoy escribiendo un proceso que archiva las filas de una tabla de SQL Server basada en una columna de fecha y hora. Quiero mover todas las filas con una fecha anterior a X, pero el problema es que hay millones de filas para cada fecha, por lo que hacer BEGIN TRANSACTION ... INSERTAR ... ELIMINAR ... COMPROMETER para cada fecha lleva demasiado tiempo y bloquea la base de datos para otros usuarios.Mover datos de SQL Server en fragmentos limitados (1000 filas)

¿Hay alguna manera de hacerlo en pedazos más pequeños? Tal vez usando ROWCOUNT o algo así?

me gustaría originalmente considerados algo así:

SET ROWCOUNT 1000 

DECLARE @RowsLeft DATETIME 
DECLARE @ArchiveDate DATETIME 

SET @ROWSLEFT = (SELECT TOP 1 dtcol FROM Events WHERE dtcol <= @ArchiveDate) 

WHILE @ROWSLEFT IS NOT NULL 
BEGIN 

    INSERT INTO EventsBackups 
    SELECT top 1000 * FROM Events 

    DELETE Events 

    SET @ROWSLEFT = (SELECT TOP 1 dtcol FROM Events WHERE dtcol <= @ArchiveDate) 

END 

Pero entonces me di cuenta de que yo no puedo garantizar que las filas que estoy borrando son los que acabo de copia de seguridad. ¿O puedo ...?

ACTUALIZACIÓN: Otra de las opciones que había considerado fue la adición de un paso:

  1. SELECT TOP 1000 filas que cumplen mis criterios de fecha en una tabla temporal
  2. iniciar la transacción
  3. Insertar temp tabla en la tabla de archivos
  4. Eliminar de la tabla fuente, unir a la tabla temporal en cada columna
  5. Commit transa cción
  6. Repetir 1-5 hasta que no quedan filas que cumplan los criterios de fecha

¿Alguien tiene una idea de cómo el gasto de esta serie podría comparar con algunas de las otras opciones que se mencionan a continuación?

DETALLE: Estoy usando SQL 2005, ya que alguien me preguntó.

+0

las cláusulas OUTPUT e INTO son su amigo, búsquelas o vea mi respuesta ... –

Respuesta

16

basta con insertar el resultado de la Borrar:

WHILE 1=1 
BEGIN 

    WITH EventsTop1000 AS (
    SELECT TOP 1000 * 
     FROM Events 
     WHERE <yourconditionofchoice>) 
    DELETE EventsTop1000 
     OUTPUT DELETED.* 
     INTO EventsBackup; 

    IF (@@ROWCOUNT = 0) 
     BREAK; 
END 

Esta es atómico y consistente.

+0

Esta debería ser la respuesta aceptada. –

0

¿Qué tal:

INSERT INTO EventsBackups 
SELECT TOP 1000 * FROM Events ORDER BY YourKeyField 

DELETE Events 
WHERE YourKeyField IN (SELECT TOP 1000 YourKeyField FROM Events ORDER BY YourKeyField) 
+0

Como un lado, este es un caso perfecto para la división de ventanas deslizantes, si puede hacer uso de ella: http : //weblogs.sqlteam.com/dang/archive/2008/08/30/Sliding-Window-Table-Partitioning.aspx Es un conmutador de metadatos, por lo que toda la carga se puede realizar en unos pocos segundos como máximo. –

0

¿Qué hay de no hacerlo todo a la vez?

INSERT INTO EventsBackups 
SELECT * FROM Events WHERE date criteria 

Luego, más tarde,

DELETE FROM Events 
SELECT * FROM Events INNER JOIN EventsBackup on Events.ID = EventsBackup.ID 

o el equivalente.

Nada de lo que ha dicho hasta ahora sugiere que necesita una transacción.

+0

Requiere demasiados recursos para hacer una inserción masiva como esa en una tabla muy activa. Necesita ser "fragmentado" para evitar grandes esperas de recursos. –

+0

Pero es la tabla de Respaldo la que se bloqueará, no la de Eventos. Por lo tanto, ¿está bloqueando un problema? Luego, más adelante, puede realizar las eliminaciones en fragmentos si están en su copia de seguridad. –

+0

Estoy usando la transacción para poder deshacer la inserción si la eliminación falla. No quiero que aparezcan en la tabla de archivos registros que todavía estén en la tabla activa, ya que eso podría generar duplicados más adelante. En realidad, estoy tratando de evitar el proceso de archivo interno increíblemente engorroso de una aplicación, que nunca tuvo la intención de manejar tanta información como la que tenemos, y quiero evitar cualquier cosa que pueda romperla. – SqlRyan

0

¿Tiene un índice en el campo de fecha? Si no tiene SQL, puede verse obligado a actualizar a un bloqueo de tabla que bloqueará a todos sus usuarios mientras se ejecutan sus declaraciones de archivo.

¡Creo que necesitará un índice para que esta operación funcione bien! ¡Pon un índice en tu campo de fecha y prueba tu operación nuevamente!

+0

Estoy usando SQL 2005, y no hay ningún índice en la tabla, lo que hace que las declaraciones SELECT sean costosas para empezar. – SqlRyan

0

¿Podría hacer una copia de Eventos, mover todas las filas con fechas > = x a eso, soltar Eventos y renombrar la copia Eventos? ¿O copiar, truncar y luego copiar de nuevo? Si puede permitirse un poco de tiempo de inactividad, este sería probablemente el enfoque más rápido.

4

utilice un aditamento con una cláusula OUTPUT INTO para almacenar los identificadores de las filas insertadas, entonces BORRAR unirse a esta tabla temporal para eliminar únicamente los identificadores de

DECLARE @TempTable (YourKeyValue KeyDatatype not null) 

INSERT INTO EventsBackups 
    (columns1,column2, column3) 
    OUTPUT INSERTED.primaryKeyValue 
    INTO @TempTable 
    SELECT 
     top 1000 
     columns1,column2, column3 
     FROM Events 

DELETE Events 
    FROM Events 
     INNER JOIN @TempTable t ON Events.PrimaryKey=t.YourKeyValue 
+0

Me gusta esta solución. Tenga en cuenta que su última unirse será: EN Events.PrimaryKey = t.primaryKeyValue en lugar de EN Events.PrimaryKey = t.YourKeyValue sólo para mantener constante el ejemplo ;-) –

+0

@Aaron Alton, t.YourKeyValue viene de mi @tempTable, que defino en mi código, no hay @TempTable .primaryKeyValue. El valor OUTPUT INSERTED.primaryKeyValue necesita ser INSERTADO. Este valor clave. –

+0

También me gusta esta solución, excepto que no hay una columna que sea clave. No puede haber filas repetidas en la tabla con la misma marca de tiempo :( Me gusta mucho, sin embargo, y vale la pena un voto positivo. – SqlRyan

0

Esto es lo que terminé haciendo:

SET @CleanseFilter = @startdate 
WHILE @CleanseFilter IS NOT NULL 
BEGIN 
    BEGIN TRANSACTION 

     INSERT INTO ArchiveDatabase.dbo.MyTable 
     SELECT * 
      FROM dbo.MyTable 
     WHERE startTime BETWEEN @startdate AND @CleanseFilter 

     DELETE dbo.MyTable 
     WHERE startTime BETWEEN @startdate AND @CleanseFilter 

    COMMIT TRANSACTION 

    SET @CleanseFilter = (SELECT MAX(starttime) 
       FROM (SELECT TOP 1000 
          starttime 
        FROM dbo.MyTable 
         WHERE startTime BETWEEN @startdate AND @enddate 
        ORDER BY starttime) a) 
END 

No estoy tirando exactamente 1000, solo 1000ish, por lo que maneja las repeticiones en la columna de tiempo adecuadamente (algo que me preocupaba cuando consideré usar ROWCOUNT). Como a menudo hay repeticiones en la columna de tiempo, veo que mueven regularmente 1002 o 1004 filas/iteración, así que sé que está obteniendo todo.

Lo presento como una respuesta para que pueda ser juzgado en comparación con las otras soluciones que las personas han proporcionado. Avíseme si hay algo obviamente mal con este método. Gracias por su ayuda, a todos, y aceptaré la respuesta que tenga más votos en pocos días.

+0

si no tiene clave, y no quiere agregar una, use mi respuesta, pero cámbiela . haga la eliminación con OUTPUT INTO, simplemente capture todas las columnas en la tabla temporal y luego inserte desde una selección de esa tabla temporal. –

+0

Debe tener mucho cuidado con el SQL que ha publicado. Si no utiliza el aislamiento de transacciones SERIALIZABLE, no se garantizará que su DELETE solo borre las filas elegidas por su SELECT. Busque lecturas no repetibles y lecturas fantasmas. Si vas con el SQL que publicaste, la única forma de que SERIALIZABLE esté garantizado por el servidor SQL (sin un índice en la columna de fecha) es bloquear la tabla, lo que matará el rendimiento como nunca antes. –

+0

no se pueden editar los comentarios * para el nivel de aislamiento serializable que se ejecutará –

0

Otra opción sería agregar un procedimiento desencadenante a la tabla Eventos que no hace más que agregar el mismo registro a la tabla EventsBackup.

De esta manera, EventsBackup está siempre actualizado, y todo lo que hace es purgar periódicamente los registros de su tabla de eventos.

Cuestiones relacionadas