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.
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é:
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:
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.
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.
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 ).
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.
Sub Query
dans Where
condition, cela peut donner High Cardianility Estimate
Dans la requête complexe.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
.