web-dev-qa-db-fra.com

Comment supprimer efficacement des lignes sans utiliser Truncate Table dans une table de plus de 500 000 lignes

Disons que nous avons la table Sales avec 30 colonnes et 500 000 lignes. Je voudrais supprimer 400 000 dans la table (ceux où "toDelete='1'").

Mais j'ai quelques contraintes:

  • la table est lue/écrite "souvent" et je ne voudrais pas qu'une longue "suppression" prenne trop de temps et verrouille la table trop longtemps
  • Je dois ignorer le journal des transactions (comme avec un TRUNCATE) mais en faisant un "DELETE ... WHERE..." (je dois mettre une condition), mais je n'ai trouvé aucun moyen de le faire ...

Tout conseil serait le bienvenu pour transformer un 

DELETE FROM Sales WHERE toDelete='1'

à quelque chose de plus partitionné et éventuellement journal des transactions gratuit.

22
Skippy Fastol

L'appel de DELETE FROM TableName fera la suppression complète en une seule transaction importante. C'est cher.

Voici une autre option qui supprimera les lignes par lots:

deleteMore:
DELETE TOP(10000) Sales WHERE toDelete='1'
IF @@ROWCOUNT != 0
    goto deleteMore
32
Kevin Aenmey

Ce que vous voulez, c'est un traitement par lots. 

While (select Count(*) from sales where toDelete =1) >0
BEGIN
Delete from sales where SalesID in
(select top 1000 salesId from sales where toDelete = 1)
END

Bien sûr, vous pouvez expérimenter quelle est la meilleure valeur à utiliser pour le lot. J'ai utilisé entre 500 et 50000 en fonction du tableau. Si vous utilisez la suppression en cascade, vous aurez probablement besoin d'un nombre plus petit car vous avez ces enregistrements enfants à supprimer.

10
HLGEM

Par le passé, j'ai déjà eu recours à une procédure stockée ou à un script qui supprimenrecords. Répéter jusqu'à la fin.

DELETE TOP 1000 FROM Sales WHERE toDelete='1'
5
Cylindric

Je vais laisser ma réponse ici, car j'ai pu tester différentes approches pour la suppression et la mise à jour en masse (j'ai dû mettre à jour, puis supprimer 125 lignes ou plus, le serveur dispose de 16 Go de RAM, Xeon E5-2680 @ 2.7GHz, SQL Server 2012).

TL; DR : toujours mettre à jour/supprimer par clé primaire. Si vous ne pouvez pas utiliser directement PK, créez une table temporaire et remplissez-la de valeurs PK, puis mettez à jour/supprimez votre table à l'aide de cette table. Utilisez des index pour cela.

J'ai commencé avec la solution de ci-dessus (par @Kevin Aenmey), mais cette approche s'est révélée inappropriée, car ma base de données était active et traitait quelques centaines de transactions à la seconde, ce qui entraînait un blocage. (Il y avait un index pour tous les champs de condition, en utilisant WITH(ROWLOCK) ne changeait rien).

J'ai donc ajouté une instruction WAITFOR , qui permettait à la base de données de traiter d'autres transactions.

deleteMore:
WAITFOR DELAY '00:00:01'
DELETE TOP(1000) FROM MyTable WHERE Column1 = @Criteria1 AND Column2 = @Criteria2 AND Column3 = @Criteria3
IF @@ROWCOUNT != 0
    goto deleteMore

Cette approche a permis de traiter environ 1,6 million de lignes/heure pour la mise à jour et environ 0,2 million de lignes/heure pour la suppression.

Les tables temporaires ont beaucoup changé.

deleteMore:
SELECT TOP 10000 Id 
  INTO #Temp 
  FROM MyTable WHERE Column1 = @Criteria1 AND Column2 = @Criteria2 AND Column3 = @Criteria3 

DELETE MT
  FROM MyTable MT
  JOIN #Temp T ON T.Id = MT.Id 

/* you can use IN operator, it doesn't change anything
 DELETE FROM MyTable WHERE Id IN (SELECT Id FROM #Temp)

 */
IF @@ROWCOUNT > 0 BEGIN
    DROP TABLE #Temp
    WAITFOR DELAY '00:00:01'
    goto deleteMore
END ELSE BEGIN
    DROP TABLE #Temp
    PRINT 'This is the end, my friend'
END

Cette solution a traité environ 25 millions de lignes/heure pour la mise à jour (15 fois plus rapide) et environ 2,2 millions de lignes/heure pour la suppression (11 fois plus rapide).

2
Marko Juvančič

Vous devriez essayer de lui donner un indice ROWLOCK afin qu'il ne verrouille pas la table entière. Toutefois, si vous supprimez un grand nombre de lignes, une escalade des verrous se produira.

Assurez-vous également que la colonne toDelete contient un index non filtré filter (uniquement pour 1). Si possible, faites-en un bit de colonne, pas varchar (ou ce que c'est maintenant).

DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'

En fin de compte, vous pouvez essayer de parcourir la table et supprimer des morceaux.

Mis à jour

Étant donné que tant que les boucles et les suppressions de morceau sont la nouvelle couleur rose ici, je vais également ajouter ma version (combinée à ma réponse précédente):

SET ROWCOUNT 100
DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'

WHILE @@rowcount > 0
BEGIN
  SET ROWCOUNT 100
  DELETE FROM Sales WITH(ROWLOCK) WHERE toDelete='1'  
END
2
Marcel N.

Mon propre point de vue sur cette fonctionnalité serait le suivant… .. Ainsi, il n'y a pas de code répété et vous pouvez gérer la taille de votre bloc.

DECLARE @DeleteChunk INT = 10000
DECLARE @rowcount INT = 1

WHILE @rowcount > 0
BEGIN

  DELETE TOP (@DeleteChunk) FROM Sales WITH(ROWLOCK)

  SELECT @rowcount = @@RowCount
END
1
WaitForPete

J'ai utilisé ce qui suit pour supprimer environ 50 millions d'enregistrements -

BEGIN TRANSACTION     
     DeleteOperation:
     DELETE TOP (BatchSize)
     FROM  [database_name].[database_schema].[database_table] 

     IF @@ROWCOUNT > 0
     GOTO DeleteOperation
COMMIT TRANSACTION

Veuillez noter que garder le BatchSize <5000 coûte moins cher en ressources.

0
Ankush

Comme je le suppose, le meilleur moyen de supprimer une quantité énorme d’enregistrements est de le supprimer par Primary Key. (Qu'est-ce que Primary Keyvoir ici )

Vous devez donc générer un script tsql contenant la liste complète des lignes à supprimer, puis exécuter ce script.

Par exemple, le code ci-dessous va générer ce fichier

GO
SET NOCOUNT ON

SELECT   'DELETE FROM  DATA_ACTION WHERE ID = ' + CAST(ID AS VARCHAR(50)) + ';' + CHAR(13) + CHAR(10) + 'GO'
FROM    DATA_ACTION
WHERE  YEAR(AtTime) = 2014

Le fichier de sortie va avoir des disques comme 

DELETE FROM  DATA_ACTION WHERE ID = 123;
GO
DELETE FROM  DATA_ACTION WHERE ID = 124;
GO
DELETE FROM  DATA_ACTION WHERE ID = 125;
GO

Et maintenant, vous devez utiliser l'utilitaire SQLCMD pour exécuter ce script.

sqlcmd -S [Instance Name] -E -d [Database] -i [Script]

Vous pouvez trouver cette approche expliquée ici https://www.mssqltips.com/sqlservertip/3566/deleting-historical-data-from-a-large-highly-concurrent-sql-server-database-table/

0