Méthodes d'accélération d'un énorme DELETE FROM <table> sans clauses

37

Utilisation de SQL Server 2005.

J'effectue une énorme DELETE FROM sans clauses Where. C'est fondamentalement équivalent à une instruction TRUNCATE TABLE - sauf que je ne suis pas autorisé à utiliser TRUNCATE. Le problème est que le tableau est énorme - 10 millions de lignes et qu'il faut plus d'une heure pour le terminer. Est-il possible d'accélérer le processus sans:

  • Utilisation de tronquer
  • Désactiver ou supprimer des index?

Le t-log est déjà sur un disque séparé.

Toutes les suggestions sont les bienvenues!

tuseau
la source
2
Si vous le faites souvent, envisagez de partitionner la table
Gaius
1
Ne pouvez-vous pas utiliser TRUNCATE car des contraintes FK font référence à la table?
Nick Chammas

Réponses:

39

Ce que vous pouvez faire, c'est supprimer des lots comme ceci:

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable

Où xxx est, par exemple, 50000

Une modification de ceci, si vous voulez supprimer un pourcentage très élevé de lignes ...

SELECT col1, col2, ... INTO #Holdingtable
           FROM MyTable WHERE ..some condition..

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable WHERE ...

INSERT MyTable (col1, col2, ...)
           SELECT col1, col2, ... FROM #Holdingtable
gbn
la source
3
@tuseau: chaque suppression nécessite un espace de log en cas d'erreur, pour revenir en arrière. Une suppression de ligne 50k nécessite moins de ressources / d'espace qu'une suppression de ligne de 10 m. Bien sûr, les sauvegardes de journaux fonctionnent toujours, etc., et prennent de la place, mais il est plus facile pour le serveur de traiter de nombreux petits lots que de perdre de gros lots.
gbn
1
Merci, la suppression par lot aide un peu, je suppose que c'est la meilleure option.
Tuseau
2
@Phil Helmer: si la suppression par lot est dans une transaction, alors il n'y a aucun gain à l'utiliser. Autrement, chaque écriture de journal est plus petite, ce qui est, tout simplement, une charge plus facile
gbn le
1
Un autre commentaire: la suppression par lots aide énormément et supprime 20 millions de lignes de 1 heure 42 minutes à 3 minutes - MAIS assurez-vous que la table a un index clusterisé! Si c'est un tas, la clause TOP crée une sorte dans le plan d'exécution qui annule toute amélioration. Cela semble évident par la suite.
tuseau
2
@Noumenon: Cela garantit que @@ ROWCOUNT est à 1
gbn
21

Vous pouvez utiliser la clause TOP pour le faire facilement:

WHILE (1=1)
BEGIN
    DELETE TOP(1000) FROM table
    IF @@ROWCOUNT < 1 BREAK
END
SQLRockstar
la source
Les accolades mettent en forme votre code
gbn
@gbn C'est sur SO. ici il est encore 101 010.
bernd_k
7

Je suis d'accord avec les suggestions pour regrouper vos suppressions en morceaux gérables si vous ne pouvez pas utiliser TRUNCATE, et j'aime bien la suggestion de déposer / créer pour son originalité, mais je suis curieux du commentaire suivant dans votre question:

C'est fondamentalement équivalent à une instruction TRUNCATE TABLE - sauf que je ne suis pas autorisé à utiliser TRUNCATE

Je suppose que la raison de cette restriction est liée à la sécurité à accorder pour tronquer directement une table et au fait qu'elle vous permettrait de tronquer des tables autres que celle qui vous concerne.

En supposant que ce soit le cas, je me demandais si le fait de créer une procédure stockée utilisant TRUNCATE TABLE et utilisant "EXECUTE AS" serait considéré comme une alternative viable à l'attribution des droits de sécurité nécessaires pour tronquer directement la table.

J'espère que cela vous donnera la vitesse dont vous avez besoin, tout en apportant une solution aux problèmes de sécurité que votre société pourrait avoir en ajoutant votre compte au rôle db_ddladmin.

Un autre avantage de l’utilisation d’une procédure stockée de cette manière est que celle-ci peut être verrouillée afin que seuls les comptes spécifiques puissent l’utiliser.

Si, pour une raison quelconque, ce n'est pas une solution acceptable et que vous devez supprimer les données de cette table une fois par jour / heure / etc, je demanderais qu'un travail d'agent SQL ait été créé pour tronquer la table. à une heure programmée chaque jour.

J'espère que cela t'aides!

Jeff
la source
5

Sauf tronquer .. seulement supprimer par lots peut vous aider.

Vous pouvez supprimer la table et la recréer, avec toutes les contraintes et tous les index, bien entendu. Dans Management Studio, vous avez la possibilité d’écrire dans un tableau une table à supprimer et à créer. Il s’agit donc d’une option triviale. Mais ceci uniquement si vous êtes autorisé à effectuer des actions DDL, ce qui, à mon avis, n'est pas vraiment une option.

Marian
la source
Étant donné que l'application est conçue pour des opérations simultanées, la modification de la structure (DDL) et l'utilisation de troncature ne sont pas des options ... Je suppose que la suppression par lots est la meilleure disponible. Merci quand même.
tuseau
1

Étant donné que cette question est une référence si importante, je publie ce code qui m'a vraiment aidé à comprendre la suppression avec des boucles ainsi que la messagerie dans une boucle pour suivre les progrès.

La requête est modifiée à partir de cette question en double. Crédit à @RLF pour la base de requêtes.

CREATE TABLE #DelTest (ID INT IDENTITY, name NVARCHAR(128)); -- Build the test table
INSERT INTO #DelTest (name) SELECT name FROM sys.objects;  -- fill from system DB
SELECT COUNT(*) TableNamesContainingSys FROM #deltest WHERE name LIKE '%sys%'; -- check rowcount
go
DECLARE @HowMany INT;
DECLARE @RowsTouched INT;
DECLARE @TotalRowCount INT;
DECLARE @msg VARCHAR(100);
DECLARE @starttime DATETIME 
DECLARE @currenttime DATETIME 

SET @RowsTouched = 1; -- Needs to be >0 for loop to start
SET @TotalRowCount=0  -- Total rows deleted so far is 0
SET @HowMany = 5;     -- Variable to choose how many rows to delete per loop
SET @starttime=GETDATE()

WHILE @RowsTouched > 0
BEGIN
   DELETE TOP (@HowMany)
   FROM #DelTest 
   WHERE name LIKE '%sys%';

   SET @RowsTouched = @@ROWCOUNT; -- Rows deleted this loop
   SET @TotalRowCount = @TotalRowCount+@RowsTouched; -- Increment Total rows deleted count
   SET @currenttime = GETDATE();
   SELECT @msg='Deleted ' + CONVERT(VARCHAR(9),@TotalRowCount) + ' Records. Runtime so far is '+CONVERT(VARCHAR(30),DATEDIFF(MILLISECOND,@starttime,@currenttime))+' milliseconds.'
   RAISERROR(@msg, 0, 1) WITH NOWAIT;  -- Print message after every loop. Can't use the PRINT function as SQL buffers output in loops.  

END; 
SELECT COUNT(*) TableNamesContainingSys FROM #DelTest WHERE name LIKE '%sys%'; -- Check row count after loop finish
DROP TABLE #DelTest;
Max xaM
la source