2010-08-06 11 views
6

Tengo una tabla cats con 42,795,120 filas.¿ELIMINAR SQL con una subconsulta correlacionada para la tabla con 42 millones de filas?

Aparentemente, esta es una gran cantidad de filas. Así que cuando hago:

/* owner_cats is a many-to-many join table */ 
DELETE FROM cats 
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats 
WHERE owner_cats.id_owner = 1) 

los tiempos de consulta fuera :(

(edición: Necesito aumentar mi valorCommandTimeout, defecto es sólo 30 segundos)

I no puedo usar TRUNCATE TABLE cats porque no quiero volar gatos de otros propietarios.

Estoy usando S "Simple" QL Server 2005 con "modelo de recuperación" ajustado a

Por lo tanto, pensé en hacer algo como esto (la ejecución de este SQL desde una aplicación por cierto):

DELETE TOP (25) PERCENT FROM cats 
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats 
WHERE owner_cats.id_owner = 1) 

DELETE TOP(50) PERCENT FROM cats 
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats 
WHERE owner_cats.id_owner = 1) 

DELETE FROM cats 
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats 
WHERE owner_cats.id_owner = 1) 

Mi pregunta es: ¿qué es el umbral de la cantidad de filas que puedo DELETE en SQL Server 2005?

O, si mi enfoque no es óptimo, sugiera un mejor enfoque. Gracias.

Este post no me ayuda lo suficiente:

EDITAR (8/6/2010):

bien, me di cuenta después de leer el enlace de arriba otra vez que no tenía índices en estas tablas. Además, algunos de ustedes ya han señalado ese problema en los comentarios a continuación. Tenga en cuenta que este es un esquema ficticio, por lo que incluso id_cat no es un PK, porque en mi esquema de la vida real, no es un campo único.

pondré índices en:

  1. cats.id_cat
  2. owner_cats.id_cat
  3. owner_cats.id_owner

supongo que todavía estoy tomando la mano de este almacenamiento de datos, y obviamente necesito índices en todos los campos JOIN ¿verdad?

Sin embargo, me lleva horas realizar este proceso de carga por lotes. Ya lo estoy haciendo como SqlBulkCopy (en fragmentos, no 42 mil a la vez). Tengo algunos índices y PKs.He leído los siguientes mensajes que confirma mi teoría de que los índices están desacelerando, incluso una copia masiva:

así que voy a DROP mis índices antes de la copia y luego re CREATE cuando esté listo.

Debido a los largos tiempos de carga, me tomará un tiempo probar estas sugerencias. Informaré con los resultados.

ACTUALIZACIÓN (8/7/2010):

Tom sugirió:

DELETE 
FROM cats c 
WHERE EXISTS (SELECT 1 
FROM owner_cats o 
WHERE o.id_cat = c.id_cat 
AND o.id_owner = 1) 

Y aún sin índices, por 42 millones de filas, se tardó 13:21 min: s frente a 22 : 08 con la forma descrita arriba. Sin embargo, durante 13 millones de filas, lo tomó 2:13 frente a 2:10 a la antigua. Es una buena idea, ¡pero aún necesito usar índices!

Update (8/8/2010):

Algo está terriblemente mal! Ahora con los índices activados, mi primera consulta de eliminación anterior tomó 1: 9 horas: mín. (¡sí una hora!) versus 22:08 min: seg y 13:21 min: seg versus 2:10 min: seg para 42 mil filas y 13 mil filas respectivamente. Voy a probar la consulta de Tom con los índices ahora, pero esto va en la dirección incorrecta. Por favor ayuda.

Update (8/9/2010):

de Tom delete tomó 1:06 hrs: min durante 42 filas mil y 10:50 min: seg para 13 filas mil con índices frente a 13:21 min : sec y 2:13 min: seg respectivamente. ¡Las eliminaciones tardan más tiempo en mi base de datos cuando uso índices por un orden de magnitud! Creo que sé por qué, mi base de datos .mdf y .ldf creció de 3.5 GB a 40.6 GB durante la primera eliminación (42 mil)! ¿Qué estoy haciendo mal?

Update (8/10/2010):

A falta de cualquier otra opción, me han llegado con lo que siento es una solución mediocre (esperemos que temporal):

  1. Aumentar el tiempo de espera para la conexión a la base de datos de 1 hora (CommandTimeout=60000; defecto fue de 30 seg)
  2. consulta de uso Tom: DELETE FROM WHERE EXISTS (SELECT 1 ...), ya que lleva a cabo un poco más rápido
  3. DROP todos los índices y PK antes de ejecutar la instrucción delete (???)
  4. Run DELETE comunicado
  5. CREATE todos los índices y PK

parece una locura, pero al menos es más rápido que usar TRUNCATE y empezar de mi carga desde el principio con el primer owner_id, porque uno de mi owner_id tarda 2:30 h: min para cargar en comparación con 17:22 min: seg para el proceso de eliminación que acabo de describir con filas de 42 mil. (Nota: si mi proceso de carga arroja una excepción, empiezo de nuevo por ese owner_id, pero no quiero volar el anterior owner_id, así que no quiero TRUNCATE la tabla owner_cats, por lo cual intento utilizar DELETE.)

aún más ayuda sería apreciada :)

+1

¿Puede explicar lo que tiene para los índices en sus tablas? – bobs

+4

No soy un enemigo de los gatos, pero no son muchas las filas, pero son muchos gatos :) Y, esto me rompe "No quiero que los gatos se deshagan de otros propietarios" – bobs

+2

¿Esto es así? Base de datos CrazyOldLady? –

Respuesta

6

no existe un umbral práctico. Depende de lo que su tiempo de espera de comando se establece en su conexión.

Tenga en cuenta que el tiempo que se necesita para eliminar todas estas filas está supeditada a:

  • El tiempo que se tarda en encontrar las filas de interés
  • El tiempo que tarda en registrar la transacción en el registro de transacciones
  • el tiempo que se tarda en eliminar las entradas de índice de interés
  • el tiempo que se tarda en eliminar las filas reales de interés
  • el tiempo que se necesita para esperar a otros procesos para dejar de usar la tabla para poder adquirir lo que en este caso probablemente sea una cerradura de tabla exclusiva

El último punto puede ser a menudo el más significativo. Haga un comando sp_who2 en otra ventana de consulta para asegurarse de que no haya contención de bloqueo, evitando que su comando se ejecute.

Los Servidores SQL mal configurados tendrán un mal rendimiento en este tipo de consultas. Los registros de transacciones que son demasiado pequeños y/o comparten los mismos discos que los archivos de datos a menudo incurrirán en severas penalizaciones de rendimiento cuando se trabaja con filas grandes.

En cuanto a una solución, bueno, como todo, depende. ¿Es esto algo que pretendes hacer a menudo? Según la cantidad de filas que le quedan, la forma más rápida podría ser reconstruir la tabla como otro nombre y luego cambiarle el nombre y recrear sus restricciones, todo dentro de una transacción. Si esto es solo algo ad-hoc, asegúrese de que su ADO CommandTimeout esté configurado lo suficientemente alto y solo pueda asumir el costo de esta gran eliminación.

+0

Bueno, debería tener mucha contención en este recuadro. No estoy configurando 'CommandTimeout', así que supongo que estoy usando el valor predeterminado de 30 segundos. Además, .ldf comparte el mismo disco que .mdf, pero probablemente pueda cambiar eso. Este es un proceso de carga por lotes, y ese 'DELETE' solo se realiza cuando se agota el tiempo de espera de una llamada al servicio web y tengo que volver a cargar los gatos solo para el propietario que estaba cargando actualmente. – JohnB

3

No hay ningún umbral como tal: puede ELIMINAR todas las filas de cualquier tabla con suficiente espacio de registro de transacciones, que es donde probablemente se cae la consulta. Si usted está recibiendo algunos resultados de su principio Eliminar (n) PORCENTAJE DE DONDE gatos ... a continuación, se puede envolver en un bucle de la siguiente manera:

SELECT 1 
WHILE @@ROWCOUNT <> 0 
BEGIN 
DELETE TOP (somevalue) PERCENT FROM cats 
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats 
WHERE owner_cats.id_owner = 1) 
END 
3

Como otros han mencionado, al eliminar 42 millones de filas , el archivo db tiene que registrar 42 millones de eliminaciones en la base de datos. Por lo tanto, el registro de transacciones debe crecer sustancialmente. Lo que podría intentar es dividir la eliminación en fragmentos. En la siguiente consulta, utilizo la función de clasificación NTile para dividir las filas en 100 segmentos.Si es demasiado lento, puede ampliar el número de depósitos para que cada eliminación sea más pequeña. Ayudará tremendamente si hay un índice en owner_cats.id_owner, owner_cats.id_cats y cats.id_cat (que asumí la clave primaria y numérica).

Declare @Cats Cursor 
Declare @CatId int --assuming an integer PK here 
Declare @Start int 
Declare @End int 
Declare @GroupCount int 

Set @GroupCount = 100 

Set @Cats = Cursor Fast_Forward For 
    With CatHerd As 
     (
     Select cats.id_cat 
      , NTile(@GroupCount) Over (Order By cats.id_cat) As Grp 
     From cats 
      Join owner_cats 
       On owner_cats.id_cat = cats.id_cat 
     Where owner_cats.id_owner = 1 
     ) 
     Select Grp, Min(id_cat) As MinCat, Max(id_cat) As MaxCat 
     From CatHerd 
     Group By Grp 
Open @Cats 
Fetch Next From @Cats Into @CatId, @Start, @End 

While @@Fetch_Status = 0 
Begin 
    Delete cats 
    Where id_cat Between @Start And @End 

    Fetch Next From @Cats Into @CatId, @Start, @End 
End 

Close @Cats 
Deallocate @Cats 

El aspecto notable con el enfoque anterior es que no es transaccional. Por lo tanto, si falla en el trozo 40, habrá eliminado el 40% de las filas y el otro 60% seguirá existiendo.

+0

Gracias, podría tener que probar esto. ¿Pero qué piensas de mi idea de 'TOP (25) PERCENT'? – JohnB

+0

@John B: la desventaja de la solución TOP X% es que debe volver a consultar/reevaluar TOP X% en cada iteración en lugar de solo una vez, como he hecho aquí. – Thomas

6

Si la eliminación eliminará "un número significativo" de filas de la tabla, esto puede ser una alternativa a un DELETE: coloque los registros para guardarlos en otro lugar, trunque la tabla original, coloque los 'keepers'. Algo así como:

SELECT * 
INTO #cats_to_keep 
FROM cats 
WHERE cats.id_cat NOT IN ( -- note the NOT 
SELECT owner_cats.id_cat FROM owner_cats 
WHERE owner_cats.id_owner = 1) 

TRUNCATE TABLE cats 

INSERT INTO cats 
SELECT * FROM #cats_to_keep 
+0

¡Buena sugerencia! – JohnB

4

Si utiliza un EXISTS en lugar de un IN, debería ser mucho mejor rendimiento. Pruebe esto:

DELETE 
    FROM cats c 
WHERE EXISTS (SELECT 1 
       FROM owner_cats o 
       WHERE o.id_cat = c.id_cat 
        AND o.id_owner = 1) 
+0

+1 ¡Ayuda! Con 42 millones de filas, todavía no hay índices, a la antigua: 22: 8 min: seg. Tu camino: 13:21. Sin embargo, con 13 millones de filas (tengo 2 propietarios) a la antigua: 2:10. Tu camino: 2:13. Un gran consejo, ¿puedes explicar cómo funciona, por favor? – JohnB

+0

Todo se trata de que el optimizador realmente maneje las cosas, pero básicamente con la cláusula IN la sub selección debe ser completamente evaluada, mientras que con EXISTS, solo se necesita la primera fila. – Tom

+1

Sin índices, siempre se rellenarán aquí. Por lo menos, necesitas poner un índice en owner_cats.id_cat, entonces esta cláusula EXISTS debería ser muy rápida. – Tom

6

¿Ha intentado no utilizar Subquery y utilizar una unión en su lugar?

DELETE cats 
FROM 
cats c 
INNER JOIN owner_cats oc 
on c.id_cat = oc.id_cat 
WHERE 
    id_owner =1 

Y si lo has hecho, también has probado diferentes consejos para unirse, p.

DELETE cats 
FROM 
cats c 
INNER HASH JOIN owner_cats oc 
on c.id_cat = oc.id_cat 
WHERE 
    id_owner =1 
+0

+1 No lo hice, pero ahora lo agradeceré! – JohnB

0

Bill Karwin's answer a otra pregunta se aplica a mi situación también:

"Si su DELETE tiene por objeto eliminar una gran mayoría de las filas de esa tabla, una cosa que la gente suele hacer es copiar sólo las filas desea mantener una tabla duplicada, y luego usar DROP TABLE o TRUNCATE para borrar la tabla original mucho más rápido ".

Matt in this answer dice de esta manera:

"Si fuera de línea y la eliminación de un gran%, puede tener sentido que acaba de crear una nueva tabla con los datos para mantener, eliminar la tabla de edad, y cambiar el nombre de"

ammoQ in this answer (de la misma pregunta) recomienda (parafraseado):

  • tema un bloqueo de tabla cuando se elimina una gran cantidad de filas
  • índices de venta sobre las columnas de clave externa
+0

El problema con las sugerencias y conceptos similares de Matt & Bill es que creo que 42 millones de filas tardarían mucho tiempo en copiarse, tal vez. – JohnB

1

<Editar> (28/09/2011)
Mi respuesta se realiza básicamente de la misma manera que la solución de Thomas (6 de agosto de 2010). Lo extrañé cuando publiqué mi respuesta porque utiliza un CURSOR real, así que pensé "malo" por el número de registros involucrados. Sin embargo, cuando volví a leer su respuesta, me doy cuenta de que el CAMINO en que usa el cursor es realmente "bueno". Muy inteligente. Acabo de votar su respuesta y probablemente usaré su enfoque en el futuro. Si no entiende por qué, échele un vistazo nuevamente. Si aún no puede verlo, publique un comentario sobre esta respuesta y volveré e intentaré explicarlo en detalle. Decidí dejar mi respuesta porque alguien puede tener un DBA que se niega a dejar que use un CURSOR real, independientemente de lo "bueno" que sea.:-)
</Editar >

que darse cuenta de que esta pregunta es un año de edad, pero recientemente he tenido una situación similar. Estaba tratando de hacer actualizaciones "masivas" a una mesa grande con unirme a una tabla diferente, también bastante grande. El problema era que la unión generaba tantos "registros unidos" que el proceso tardaba demasiado tiempo y podía haber provocado problemas de contención. Como esta fue una actualización única, se me ocurrió el siguiente "truco". Creé un WHILE LOOP que pasó por la tabla para actualizar y recogió 50,000 registros para actualizar a la vez. Se veía más o menos así:

DECLARE @RecId bigint 
DECLARE @NumRecs bigint 
SET @NumRecs = (SELECT MAX(Id) FROM [TableToUpdate]) 
SET @RecId = 1 
WHILE @RecId < @NumRecs 
BEGIN 
    UPDATE [TableToUpdate] 
    SET UpdatedOn = GETDATE(), 
     SomeColumn = t2.[ColumnInTable2] 
    FROM [TableToUpdate] t 
    INNER JOIN [Table2] t2 ON t2.Name = t.DBAName 
     AND ISNULL(t.PhoneNumber,'') = t2.PhoneNumber 
     AND ISNULL(t.FaxNumber, '') = t2.FaxNumber 
    LEFT JOIN [Address] d ON d.AddressId = t.DbaAddressId 
     AND ISNULL(d.Address1,'') = t2.DBAAddress1 
     AND ISNULL(d.[State],'') = t2.DBAState 
     AND ISNULL(d.PostalCode,'') = t2.DBAPostalCode 
    WHERE t.Id BETWEEN @RecId AND (@RecId + 49999) 
    SET @RecId = @RecId + 50000 
END 

Nada sofisticado, pero hizo su trabajo. Debido a que solo procesaba 50,000 registros a la vez, todos los bloqueos que se crearon fueron de corta duración. Además, el optimizador se dio cuenta de que no tenía que hacer toda la tabla, por lo que hizo un mejor trabajo al elegir un plan de ejecución.

<Editar> (9/28/2011)
hay un problema enorme a la sugerencia de que se ha mencionado aquí más de una vez y está publicado por todo el lugar alrededor de la web con respecto a la copia de la "buena" registra en una tabla diferente, haciendo un TRUNCATE (o DROP y reCREATE, o DROP y renombra) y luego repoblando la tabla.

No puede hacer esto si la tabla es la tabla PK en una relación PK-FK (u otra RESTRICCIÓN). De acuerdo, podrías BAJAR la relación, hacer la limpieza y restablecer la relación, pero también deberías limpiar la tabla FK. Puedes hacerlo ANTES de restablecer la relación, lo que significa más "tiempo de inactividad", o puedes optar por NO HACER EFECTIVA la RESTRICCIÓN en la creación y limpiarla después. Supongo que también puedes limpiar la tabla FK ANTES de limpiar la tabla PK. La conclusión es que tienes que limpiar explícitamente la tabla FK, de una manera u otra.

Mi respuesta es un proceso híbrido basado en SET/cuasi CURSOR. Otro beneficio de este método es que si la relación PK-FK está configurada para CASCADE BORRAR, no tiene que hacer la limpieza mencionada anteriormente porque el servidor se encargará de usted. Si su empresa/DBA no recomienda las eliminaciones en cascada, puede solicitar que solo se activen mientras se está ejecutando este proceso y luego se desactiva cuando termina. Dependiendo de los niveles de permiso de la cuenta que ejecuta la limpieza, las instrucciones ALTER para habilitar/deshabilitar las eliminaciones en cascada se pueden agregar al principio y al final de la instrucción SQL. </Editar >

3

podría ser vale la pena probar, por ejemplo, MERGE

MERGE INTO cats 
    USING owner_cats 
     ON cats.id_cat = owner_cats.id_cat 
     AND owner_cats.id_owner = 1 
WHEN MATCHED THEN DELETE; 
+0

No tenía conocimiento del 'MERGE' T-SQL. Gracias por la sugerencia; Lo intentaré y publicaré los resultados cuando tenga oportunidad. – JohnB

Cuestiones relacionadas