web-dev-qa-db-fra.com

Réglage d'une énorme opération Supprimer sur la table SQL Server

J'effectue une opération de suppression sur une très grande table SQL Server basée sur la requête comme indiqué ci-dessous.

delete db.st_table_1
where service_date between(select min(service_date) from stg_table)
                   and (select max(service_date) from stg_table);

stg_table et stg_table_1 n'a pas d'index sur service_date.

ces deux tables sont chargées de millions de lignes de données et de suppression de l'opération prend beaucoup de temps. Demander votre suggestion d'améliorer la performance de cette requête.

J'ai évoqué la stratégie décrite dans la question ci-dessous mais je ne pouvais pas comprendre comment la mettre en œuvre.

Comment supprimer une grande quantité de données dans SQL Server sans perte de données?

demander votre aimable suggestion à ce sujet.

Mettre à jour:

select * into db.temp_stg_table_1
from db.stg_table_1
where service_date not between( select min(service_date) from db.stg_table)
                             and (select max(service_date) from db.stg_table);

exec sp_rename 'stg_table_1' , 'stg_table_1_old'

exec sp_rename 'temp_stg_table_1' , 'test_table_1'

drop table stg_table_1_old

que diriez-vous d'aller avec la logique ci-dessus pour supprimer les millions d'enregistrements. tout avantage et inconvénients avec cela.

3
vikrant rana

Test basé sur vos commentaires

Testé sur SQL Server 2014 SP3

stg_table et stg_table_1 n'a pas d'index sur service_date.

ces deux tables sont chargées de millions de lignes de données et de suppression de l'opération prend beaucoup de temps.

DDL

CREATE TABLE dbo.st_table_1( stg_table_1_ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                             service_date datetime2,
                            val  int)
CREATE TABLE dbo.stg_table (stg_table_ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                            service_date datetime2,
                            val  int)

PK + index en cluster sur les champs d'identité.

DML

INSERT INTO dbo.stg_table WITH(TABLOCK)
(
service_date,val) 
SELECT -- 1M
 DATEADD(S,rownum,GETDATE()),rownum
 FROM
 (SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2) as sptvalues

INSERT INTO dbo.st_table_1 WITH(TABLOCK)
(
service_date,val) 
SELECT -- 2.5M
 DATEADD(S,rownum,GETDATE()),rownum
 FROM
 (SELECT TOP(2500000) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2) as sptvalues

INSERT INTO dbo.stg_table WITH(TABLOCK)
(
service_date,val) 
SELECT -- 4M
 DATEADD(S,rownum,GETDATE()),rownum
 FROM
 (SELECT TOP(4000000) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2) as sptvalues

Des lignes de 2,5 m dans dbo.st_table_1 et 5m rangées dans dbo.stg_table (presque) toutes ces lignes de 2,5 m seront supprimées par la requête qui est de plus de 10 fois inférieure à la vôtre.

Exécution de votre requête

le plan d'exécution actuel pour votre déclaration de suppression de base

Comme prévu dbo.stg_table est accessible deux fois pour obtenir les valeurs max et min avec un ensemble d'agrégats de flux. Le temps de la CPU et l'heure écoulée/d'exécution:

  CPU time = 4906 ms,  elapsed time = 4919 ms.

Un indice d'index manquant est ajouté au plan d'exécution:

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[st_table_1] ([service_date])
INCLUDE ([stg_table_1_ID])

Toutefois, lorsque nous ajoutons l'index, une sorte supplémentaire semble supprimer les lignes de cet index nouvellement ajouté:

enter image description here

le plan

Et le temps de la CPU/temps écoulé augmente:

   CPU time = 11156 ms,  elapsed time = 11332 ms.

YMMV , mais de mon exemple, en fonction de vos commentaires sur les données, cela n'a pas amélioré la requête.

Création d'un index sur [dbo].[stg_table]

CREATE NONCLUSTERED INDEX IX_service_date
ON [dbo].[stg_table] ([service_date]);

En conséquence, la fonction MAX() et MIN() peut tirer parti de l'index nouvellement créé pour ne renvoyer qu'une seule ligne au lieu d'une analyse complète de l'indice en cluster:

enter image description here

Avec le temps d'exécution amélioré:

 SQL Server Execution Times:
   CPU time = 2609 ms,  elapsed time = 4028 ms.

Et le plan d'exécution

Mais ceci est uniquement basé sur l'indexation et mon propre exemple. Procéder à vos risques et périls.


Notes supplémentaires

Vous devriez examiner la scission qui supprime des lots distincts afin de ne pas remplir le fichier journal et de ne pas avoir un gros bloc de suppression de la suppression/succédée.

Vous pouvez également envisager d'utiliser (TABLOCK) afin que toute la table soit verrouillée du tout début.

SET STATISTICS IO, TIME ON;
delete dbo.st_table_1 WITH(TABLOCK)
where service_date between(select min(service_date) from stg_table)
                   and (select max(service_date) from stg_table);

Mise à jour: SELECT INTO + sp_rename

select * into db.temp_stg_table_1
from db.stg_table_1
where service_date not between( select min(service_date) from db.stg_table)
                             and (select max(service_date) from db.stg_table);

exec sp_rename 'stg_table_1' , 'stg_table_1_old'

exec sp_rename 'temp_stg_table_1' , 'test_table_1'

drop table stg_table_1_old

que diriez-vous d'aller avec la logique ci-dessus pour supprimer les millions d'enregistrements. tout avantage et inconvénients avec cela.

Outre les performances, sp_rename a besoin d'un verrou Sch-M pour compléter, ce qui signifie qu'il doit attendre que toutes les autres sessions libèrent leurs serrures sur la table avant de pouvoir modifier. Tous les index/contraintes sur la table d'origine seront partis et vous devrez les recréer.

Quand j'exécute la requête sur mes propres données:

select * into dbo.temp_stg_table_1
from dbo.st_table_1
where service_date not between( select min(service_date) from dbo.stg_table)
                             and (select max(service_date) from dbo.stg_table);

Cela ne représente pas vos données, gardez cela à l'esprit.

Il lit toutes les lignes de retourner 0 qui n'est pas optimale.

enter image description here

Avec un temps d'exécution élevé:

 SQL Server Execution Times:
   CPU time = 27717 ms,  elapsed time = 10657 ms.

Mais ce n'est pas vraiment significatif sans plus d'informations sur vos données. Un plan de requête serait nécessaire pour donner plus de conseils corrects.

6
Randi Vertongen

Je ne supprimerais jamais 37 millions de lignes dans une déclaration . Cela ne concerne pas le plan d'exécution que vous obtenez - la surcharge de la recherche de lignes à supprimer (que vous ayez que le paramètre reniflant affecte la découverte de ces lignes ou non) est beaucoup plus bas que la surcharge de la suppression réellement et de la journalisation de ces suppressions. Si vous divisez cela en morceaux, vous pouvez amortir cela coûtant au fil du temps et traiter les suppressions sur un calendrier non-costume - votre fantaisie au lieu de tous.

-- you can play with these parameters to see what offers the best trade-off
DECLARE @BatchSize int = 10000, @TransactionInterval tinyint = 5;

DECLARE @s datetime, @e datetime, @r int = 1;

SELECT @s = MIN(service_date), @e = MAX(service_date) FROM dbo.stg_table;

BEGIN TRANSACTION;

WHILE (@r > 0)
BEGIN
  IF @r % @TransactionInterval = 1
  BEGIN
    COMMIT TRANSACTION;
    BEGIN TRANSACTION;
  END

  DELETE TOP (@BatchSize) FROM db.st_table_1
    WHERE service_date >= @s AND service_date <= @e;

  SET @r = @@ROWCOUNT;
END

IF @@TRANCOUNT > 0
BEGIN
  COMMIT TRANSACTION;
END

Vous pouvez également envisager une durabilité retardée si vous êtes sur une version suffisante moderne de SQL Server (voir cette réponse et ce blog post ).

1
Aaron Bertrand

Au-dessus de la requête peut effectuer OK en raison de l'index manquant, mais la requête est toujours fausse.

Declare @Fromdate DateTime
Declare @Todate DateTime

select @Fromdate=min(service_date),@Todate=max(service_date) 
from dbo.stg_table

SET STATISTICS IO, TIME ON;
delete dbo.st_table_1 WITH(TABLOCK)
where service_date >=@Fromdate
                  and service_date <=@Todate

J'ai pris au-dessus de l'exemple et exécuté sans index, il a fallu 18 secondes pour supprimer les lignes 410792.

Si je crée index comme ci-dessus aucun doute, il sera meilleur.

  1. Donc non Sub Query dans Where condition, cela peut donner High Cardianility Estimate Dans la requête complexe.
  2. Donner plus d'importance par écrit Optimize query que index. Les deux sont importants.

NOTE :

Si la performance est mauvaise ou pire à cause de Parameter Sniffing Alors seulement vous devriez trouver un moyen approprié d'éviter Parameter sniffing, sinon tu devrais l'ignorer.

Après tout, tous les Store Procedure est écrit avec OPTION RECOMPILE.

Autant que je sache, dans mon script @FromDate et @Todate ne sont pas un paramètre de procréation qu'ils sont une variable locale, il n'y a donc aucune question de Parameter Sniffing.

0
KumarHarsh