Comment supprimer de grandes données de table en SQL sans journal?


127

J'ai une grande table de données. Il y a 10 millions d'enregistrements dans ce tableau.

Quel est le meilleur moyen pour cette requête

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())

4
:) J'ai peur à moins que vous ne souhaitiez écrire une sorte d'ETL pour obtenir toutes les lignes readTime> = dateadd (MONTH, -7, GETDATE ()) dans une autre table, puis émettez une table Tronquer et remettre les données en utilisant ETL , vous ne pourriez pas l'empêcher d'écrire dans le journal
TMNT2014

La journalisation est une fonction tout ou rien d'avoir des transactions résilientes. Cela n'a littéralement pas de sens de ne pas avoir de journal pour certaines opérations mais pas pour d'autres, sinon le journal est inutile.
Erik Philips

1
Exportez les données que vous souhaitez conserver, tronquez la table, puis réimportez-la en
Bohème

Une autre option consisterait à utiliser une tablevariable qui n'est pas enregistrée. Par conséquent, stockez vos données readTime> = dateadd (MONTH, -7, GETDATE ()) dans une variable de table, puis tronquez la table d'origine et recopiez les données de la variable de table. Je garderais cependant une sauvegarde des données au cas où quelque chose n'allait pas et que la table serait tronquée par inadvertance. :) Et faites toujours un test de votre script sur un environnement moindre.
TMNT2014

Réponses:


203
  1. Si vous supprimez toutes les lignes de cette table, l'option la plus simple consiste à tronquer la table, quelque chose comme

    TRUNCATE TABLE LargeTable
    GO
    

    Tronquer la table videra simplement la table, vous ne pouvez pas utiliser la clause WHERE pour limiter les lignes supprimées et aucun déclencheur ne sera déclenché.

  2. D'autre part, si vous supprimez plus de 80 à 90% des données, par exemple, si vous avez un total de 11 millions de lignes et que vous souhaitez supprimer 10 millions, une autre façon serait d'insérer ces 1 million de lignes (enregistrements que vous souhaitez conserver ) vers une autre table intermédiaire. Tronquez ce grand tableau et réinsérez ces 1 million de lignes.

  3. Ou si les autorisations / vues ou d'autres objets qui ont cette grande table comme table sous-jacente ne sont pas affectés par la suppression de cette table, vous pouvez obtenir cette quantité relativement petite de lignes dans une autre table, supprimer cette table et créer une autre table avec le même schéma et les importer rangées dans cette table ex-Large.

  4. Une dernière option à laquelle je peux penser est de changer votre base de données, Recovery Mode to SIMPLEpuis de supprimer les lignes par lots plus petits en utilisant une boucle while quelque chose comme ça.

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END

et n'oubliez pas de changer le mode de récupération à plein et je pense que vous devez faire une sauvegarde pour le rendre pleinement affectif (les modes de changement ou de récupération).


14
Rappelez-vous également que si vous tronquez une table, vous ne pouvez pas lui associer de FK.
HLGEM

1
Mais comment être sûr de supprimer 80 à 90% des données? Supposons que je n'ai qu'une plage de valeurs à supprimer. Et j'ai quelques tables. Je dois donc vérifier chacun d'eux et calculer le pourcentage, et s'il est d'environ 30%, je suppose que cette méthode n'est pas très efficace ... J'essaie de trouver une solution optimale pour un cas inconnu.
Archont

7
@Archont optimal solution for unknown casec'est le rêve n'est-ce pas? Malheureusement, vous ne pouvez pas guérir toutes les maladies avec une seule pilule; J'ai suggéré des solutions possibles pour différents scénarios. Il n'y a malheureusement pas de balle en argent ici.
M.Ali

5
Une chose à noter si vous choisissez l'option 4: selon la façon dont la table est utilisée, il peut être préférable de supprimer moins de 5000 lignes à la fois pour éviter l' escalade des verrous .
Daniel

Si le nombre d'enregistrements à supprimer est beaucoup plus important que les enregistrements qui resteront dans la table, j'ai trouvé que la simple sélection dans la table temporaire des enregistrements qui resteront dans la table d'origine et le changement de nom de la table temporaire était beaucoup plus rapide. Étant donné que vous n'utilisez pas la clé étrangère de l'ID d'identité quelque part.
Vladimir Bozic

95

La réponse @ m-ali est correcte, mais gardez également à l'esprit que les journaux peuvent augmenter beaucoup si vous ne validez pas la transaction après chaque segment et effectuez un point de contrôle. Voici comment je le ferais et prendrais cet article http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes comme référence, avec des tests de performances et des graphiques:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;


WHILE (@Deleted_Rows > 0)
  BEGIN

   BEGIN TRANSACTION

   -- Delete some small number of rows at a time
     DELETE TOP (10000)  LargeTable 
     WHERE readTime < dateadd(MONTH,-7,GETDATE())

     SET @Deleted_Rows = @@ROWCOUNT;

   COMMIT TRANSACTION
   CHECKPOINT -- for simple recovery model
END

1
Cela devrait être la réponse acceptée au cas où l'espace disque disponible serait limité. Sans COMMIT TRANSACTIONet CHECKPOINTles grumes continuent de croître. Merci d'avoir précisé cela.
gkoul

+1. Notez simplement que vous voudrez peut-être comparer @Deleted_Rowsà 10000 ou vous pourriez vous retrouver avec une boucle infinie en raison de la suppression indéfinie de petits ensembles de données. Donc WHILE (@Deleted_Rows = 10000)- dès qu'il n'y a pas une "page" complète de données à supprimer, elle s'arrête. Dans votre implémentation, WHILE (@Deleted_Rows > 0)la boucle while s'exécutera à nouveau même si elle n'a supprimé qu'une seule ligne, et la prochaine exécution pourrait également trouver une ligne ou deux à supprimer - résultant en une boucle infinie.
NS du Toit le

@NSduToit la clause WHERE considère les enregistrements qui ont au moins 7 mois, donc il n'y aura pas de nouveaux enregistrements qui remplissent cette condition pendant que vous effectuez la suppression.
Francisco Goldenstein le

@FranciscoGoldenstein Eh bien, la date utilisée dans la requête sera différente à chaque itération que vous calculez à plusieurs reprises la date au sein de la WHILEboucle elle - même: dateadd(MONTH,-7,GETDATE()).
NS du Toit le

@FranciscoGoldenstein De plus, peut-être pour d'autres cas d'utilisation que celui-ci - peut-être que de nouvelles données sont ajoutées à la table sous-jacente, ce qui entraînera de nouveaux enregistrements pouvant être supprimés entre différentes itérations de la WHILEboucle.
NS du Toit le

52

Vous pouvez également utiliser GO + combien de fois vous souhaitez exécuter la même requête.

DELETE TOP (10000)  [TARGETDATABASE].[SCHEMA].[TARGETTABLE] 
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100

J'aime ça, cela fonctionne pour moi J'ai accidentellement inséré la même ligne dans une table 26 millions de fois et j'ai dû supprimer toutes les occurrences de celle-ci, qui dans une seule instruction de suppression manquait de mémoire sur le serveur, donc c'est une excellente question , s'arrêtera-t-il au milieu de la boucle s'il manque de lignes à supprimer?
ScottC

2
@ScottC, ce n'est pas une boucle, il répète simplement la requête (par lots) et si vous manquez de lignes, il ne peut rien supprimer. Mais cela ne s'arrêtera pas. vous obtiendrez quelque chose comme (0 ligne (s) affectée (s)) s'il manque de lignes que vous supprimez.
Bunkerbuster

ah, oui j'ai découvert cela environ 5 minutes après avoir posté ma question, puisque ma suppression est terminée, merci cela a été très utile!
ScottC

1
À partir de quel MS SQL Server cette syntaxe est-elle GO xxcensée fonctionner? J'obtiens une erreur "Impossible de trouver la procédure stockée ''" . Sans la GOcommande, cela fonctionne bien.
Abel

3
Hmm, il semble que je puisse l'exécuter, et il s'exécute en effet plusieurs fois, mais dans MS SQL Mgt Studio, il montre la ligne rouge bouclée avec l'erreur mentionnée (mais F5-run fonctionne alors)
Abel

11

@Francisco Goldenstein, juste une petite correction. Le COMMIT doit être utilisé après avoir défini la variable, sinon le WHILE sera exécuté une seule fois:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;

WHILE (@Deleted_Rows > 0)
BEGIN
    BEGIN TRANSACTION

    -- Delete some small number of rows at a time
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())

    SET @Deleted_Rows = @@ROWCOUNT;

    COMMIT TRANSACTION
    CHECKPOINT -- for simple recovery model

END

10

Cette variante de M.Ali fonctionne très bien pour moi. Il en supprime certains, efface le journal et se répète. Je regarde le journal grandir, tomber et recommencer.

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

C'était très utile! Je l'ai modifié pour paramétrer le # of rowspour supprimer à la fois, ainsi que la WHEREclause. Fonctionne comme un charme!
Shiva

7

Si vous êtes disposé (et capable) à implémenter le partitionnement, c'est une technique efficace pour supprimer de grandes quantités de données avec peu de temps d'exécution. Pas rentable pour un exercice ponctuel, cependant.


4

J'ai pu supprimer 19 millions de lignes de mon tableau de 21 millions de lignes en quelques minutes . Voici mon approche.

Si vous avez une clé primaire auto-incrémentée sur cette table, vous pouvez utiliser cette clé primaire.

  1. Obtenez la valeur minimale de la clé primaire de la grande table où readTime <dateadd (MONTH, -7, GETDATE ()). (Ajoutez un index sur readTime, s'il n'est pas déjà présent, cet index sera de toute façon supprimé avec la table de l'étape 3.). Permet de le stocker dans une variable 'min_primary'

  2. Insérez toutes les lignes ayant la clé primaire> min_primary dans une table intermédiaire (table mémoire si le nombre de lignes n'est pas grand).

  3. Déposez la grande table.

  4. Recréez la table. Copiez toutes les lignes de la table intermédiaire vers la table principale.

  5. Supprimez la table intermédiaire.


3

Vous pouvez supprimer de petits lots en utilisant une boucle while, quelque chose comme ceci:

DELETE TOP (10000)  LargeTable 
WHERE readTime < dateadd(MONTH,-7,GETDATE())
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

2

Une autre utilisation:

SET ROWCOUNT 1000 -- Buffer

DECLARE @DATE AS DATETIME = dateadd(MONTH,-7,GETDATE())

DELETE LargeTable  WHERE readTime < @DATE
WHILE @@ROWCOUNT > 0
BEGIN
   DELETE LargeTable  WHERE readTime < @DATE
END
SET ROWCOUNT 0

Optionnel;

Si le journal des transactions est activé, désactivez les journaux des transactions.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Syntaxe plus courte

select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
  DELETE TOP (10000) LargeTable 
  WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

1

Si vous utilisez SQL Server 2016 ou version ultérieure et si votre table comporte des partitions créées en fonction de la colonne que vous essayez de supprimer (par exemple, colonne d'horodatage), vous pouvez utiliser cette nouvelle commande pour supprimer des données par partitions.

TABLE TRONCÉE AVEC (PARTITIONS ({|} [, ... n]))

Cela supprimera les données de la ou des partitions sélectionnées uniquement et devrait être le moyen le plus efficace de supprimer des données d'une partie de la table car cela ne créera pas de journaux de transactions et sera effectué aussi rapidement que la troncature normale, mais sans supprimer toutes les données. de la table.

L'inconvénient est que si votre table n'est pas configurée avec une partition, vous devez alors aller à l'ancienne et supprimer les données avec une approche régulière, puis recréer la table avec des partitions afin que vous puissiez le faire à l'avenir, ce que j'ai fait. J'ai ajouté la création et la suppression de partition dans la procédure d'insertion elle-même. J'avais une table avec 500 millions de lignes, donc c'était la seule option pour réduire le temps de suppression.

Pour plus de détails, reportez-vous aux liens ci-dessous: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL Server 2016 Tronquer la table avec des partitions

Voici ce que j'ai fait en premier pour supprimer les données avant de pouvoir recréer la table avec des partitions contenant les données requises. Cette requête s'exécutera pendant des jours pendant la fenêtre de temps spécifiée jusqu'à ce que les données soient supprimées.

:connect <<ServerName>>
use <<DatabaseName>>

SET NOCOUNT ON;
DECLARE @Deleted_Rows INT;
DECLARE @loopnum INT;
DECLARE @msg varchar(100);
DECLARE @FlagDate datetime;
SET @FlagDate =  getdate() - 31;
SET @Deleted_Rows = 1;
SET @loopnum = 1;

/*while (getdate() < convert(datetime,'2018-11-08 14:00:00.000',120))
BEGIN
    RAISERROR( 'WAIT for START' ,0,1) WITH NOWAIT   
    WAITFOR DELAY '00:10:00'
END*/
RAISERROR( 'STARTING PURGE' ,0,1) WITH NOWAIT   

WHILE (1=1)
BEGIN
    WHILE (@Deleted_Rows > 0 AND (datepart(hh, getdate() ) >= 12 AND datepart(hh, getdate() ) <= 20)) -- (getdate() < convert(datetime,'2018-11-08 19:00:00.000',120) )
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (500000)  dbo.<<table_name>>
         WHERE timestamp_column < convert(datetime, @FlagDate,102)
         SET @Deleted_Rows = @@ROWCOUNT;
         WAITFOR DELAY '00:00:01'
         select @msg = 'ROWCOUNT' + convert(varchar,@Deleted_Rows);
         set @loopnum = @loopnum + 1
         if @loopnum > 1000
             begin 
                 begin try
                        DBCC SHRINKFILE (N'<<databasename>>_log' , 0, TRUNCATEONLY)
                        RAISERROR( @msg ,0,1) WITH NOWAIT
                 end try
                 begin catch
                     RAISERROR( 'DBCC SHRINK' ,0,1) WITH NOWAIT  
                 end catch
                 set @loopnum = 1
             end
        END
WAITFOR DELAY '00:10:00'
END 
select getdate()

0

Si je dis sans boucle, je peux utiliser une GOTOinstruction pour supprimer une grande quantité d'enregistrements à l'aide du serveur SQL. exa.

 IsRepeat:
    DELETE TOP (10000)
    FROM <TableName>
    IF @@ROWCOUNT > 0
         GOTO IsRepeat

de cette façon, vous pouvez supprimer une grande quantité de données avec une taille de suppression plus petite.

laissez-moi savoir si vous avez besoin de plus d'informations.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.