Je n'étais pas au courant de cette question lorsque j'ai répondu à la question connexe ( des transactions explicites sont-elles nécessaires dans cette boucle while? ), Mais par souci d'exhaustivité, j'aborderai ce problème ici car il ne faisait pas partie de ma suggestion dans cette réponse liée .
Étant donné que je suggère de planifier cela via un travail SQL Agent (c'est 100 millions de lignes, après tout), je ne pense pas que toute forme d'envoi de messages d'état au client (c'est-à-dire SSMS) soit idéale (bien que si c'est le cas jamais besoin d'autres projets, alors je suis d'accord avec Vladimir que l'utilisation RAISERROR('', 10, 1) WITH NOWAIT;
est la voie à suivre).
Dans ce cas particulier, je créerais une table d'état qui peut être mise à jour pour chaque boucle avec le nombre de lignes mis à jour jusqu'à présent. Et cela ne fait pas de mal de jeter l'heure actuelle pour avoir un rythme cardiaque sur le processus.
Étant donné que vous souhaitez pouvoir annuler et redémarrer le processus, Je suis las d'envelopper la mise à jour de la table principale avec la mise à jour de la table d'état dans une transaction explicite. Cependant, si vous pensez que la table d'état est toujours désynchronisée en raison de l'annulation, il est facile de rafraîchir avec la valeur actuelle en la mettant simplement à jour manuellement avec le COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
.et il y a deux tables à METTRE À JOUR (c'est-à-dire la table principale et la table d'état), nous devrions utiliser une transaction explicite pour garder ces deux tables synchronisées, mais nous ne voulons pas risquer d'avoir une transaction orpheline si vous annulez le processus à un après avoir démarré la transaction mais ne l'a pas validée. Cela devrait être sûr tant que vous n'arrêtez pas le travail de l'Agent SQL.
Comment pouvez-vous arrêter le processus sans, euh, bien, l'arrêter? En lui demandant d'arrêter :-). Oui. En envoyant au processus un "signal" (similaire à kill -3
sous Unix), vous pouvez demander qu'il s'arrête au prochain moment opportun (c'est-à-dire lorsqu'il n'y a pas de transaction active!) Et qu'il se nettoie de manière agréable et ordonnée.
Comment pouvez-vous communiquer avec le processus en cours dans une autre session? En utilisant le même mécanisme que nous avons créé pour qu'il vous communique son état actuel: la table d'état. Nous avons juste besoin d'ajouter une colonne que le processus vérifiera au début de chaque boucle afin qu'il sache s'il faut continuer ou abandonner. Et puisque l'intention est de planifier cela en tant que travail de l'Agent SQL (exécuté toutes les 10 ou 20 minutes), nous devons également vérifier au tout début, car il est inutile de remplir une table temporaire avec 1 million de lignes si le processus se poursuit pour quitter un instant plus tard et ne pas utiliser ces données.
DECLARE @BatchRows INT = 1000000,
@UpdateRows INT = 4995;
IF (OBJECT_ID(N'dbo.HugeTable_TempStatus') IS NULL)
BEGIN
CREATE TABLE dbo.HugeTable_TempStatus
(
RowsUpdated INT NOT NULL, -- updated by the process
LastUpdatedOn DATETIME NOT NULL, -- updated by the process
PauseProcess BIT NOT NULL -- read by the process
);
INSERT INTO dbo.HugeTable_TempStatus (RowsUpdated, LastUpdatedOn, PauseProcess)
VALUES (0, GETDATE(), 0);
END;
-- First check to see if we should run. If no, don't waste time filling temp table
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
PRINT 'Process is paused. No need to start.';
RETURN;
END;
CREATE TABLE #FullSet (KeyField1 DataType1, KeyField2 DataType2);
CREATE TABLE #CurrentSet (KeyField1 DataType1, KeyField2 DataType2);
INSERT INTO #FullSet (KeyField1, KeyField2)
SELECT TOP (@BatchRows) ht.KeyField1, ht.KeyField2
FROM dbo.HugeTable ht
WHERE ht.deleted IS NULL
OR ht.deletedDate IS NULL
WHILE (1 = 1)
BEGIN
-- Check if process is paused. If yes, just exit cleanly.
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
PRINT 'Process is paused. Exiting.';
BREAK;
END;
-- grab a set of rows to update
DELETE TOP (@UpdateRows)
FROM #FullSet
OUTPUT Deleted.KeyField1, Deleted.KeyField2
INTO #CurrentSet (KeyField1, KeyField2);
IF (@@ROWCOUNT = 0)
BEGIN
RAISERROR(N'All rows have been updated!!', 16, 1);
BREAK;
END;
BEGIN TRY
BEGIN TRAN;
-- do the update of the main table
UPDATE ht
SET ht.deleted = 0,
ht.deletedDate = '2000-01-01'
FROM dbo.HugeTable ht
INNER JOIN #CurrentSet cs
ON cs.KeyField1 = ht.KeyField1
AND cs.KeyField2 = ht.KeyField2;
-- update the current status
UPDATE ts
SET ts.RowsUpdated += @@ROWCOUNT,
ts.LastUpdatedOn = GETDATE()
FROM dbo.HugeTable_TempStatus ts;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN;
END;
THROW; -- raise the error and terminate the process
END CATCH;
-- clear out rows to update for next iteration
TRUNCATE TABLE #CurrentSet;
WAITFOR DELAY '00:00:01'; -- 1 second delay for some breathing room
END;
-- clean up temp tables when testing
-- DROP TABLE #FullSet;
-- DROP TABLE #CurrentSet;
Vous pouvez ensuite vérifier l'état à tout moment à l'aide de la requête suivante:
SELECT sp.[rows] AS [TotalRowsInTable],
ts.RowsUpdated,
(sp.[rows] - ts.RowsUpdated) AS [RowsRemaining],
ts.LastUpdatedOn
FROM sys.partitions sp
CROSS JOIN dbo.HugeTable_TempStatus ts
WHERE sp.[object_id] = OBJECT_ID(N'ResizeTest')
AND sp.[index_id] < 2;
Vous voulez suspendre le processus, qu'il s'exécute dans un travail SQL Agent ou même dans SSMS sur l'ordinateur de quelqu'un d'autre? Exécutez simplement:
UPDATE ht
SET ht.PauseProcess = 1
FROM dbo.HugeTable_TempStatus ts;
Vous voulez que le processus puisse recommencer? Exécutez simplement:
UPDATE ht
SET ht.PauseProcess = 0
FROM dbo.HugeTable_TempStatus ts;
METTRE À JOUR:
Voici quelques éléments supplémentaires à essayer qui pourraient améliorer les performances de cette opération. Aucun n'est garanti pour aider, mais vaut probablement la peine d'être testé. Et avec 100 millions de lignes à mettre à jour, vous avez amplement le temps / l'opportunité de tester certaines variantes ;-).
- Ajoutez
TOP (@UpdateRows)
à la requête UPDATE pour que la ligne du haut ressemble à:
UPDATE TOP (@UpdateRows) ht
Parfois, cela aide l'optimiseur à savoir combien de lignes max seront affectées afin de ne pas perdre de temps à en chercher plus.
Ajoutez une CLÉ PRIMAIRE à la #CurrentSet
table temporaire. L'idée ici est d'aider l'optimiseur avec le JOIN à la table de 100 millions de lignes.
Et juste pour l'avoir déclaré afin de ne pas être ambigu, il ne devrait pas y avoir de raison d'ajouter un PK à la #FullSet
table temporaire car c'est juste une simple table de file d'attente où la commande n'est pas pertinente.
- Dans certains cas, il est utile d'ajouter un index filtré pour aider celui
SELECT
qui alimente la #FullSet
table temporaire. Voici quelques considérations liées à l'ajout d'un tel index:
- La condition WHERE doit correspondre à la condition WHERE de votre requête, d'où
WHERE deleted is null or deletedDate is null
- Au début du processus, la plupart des lignes correspondront à votre condition WHERE, donc un index n'est pas très utile. Vous voudrez peut-être attendre quelque part autour de la barre des 50% avant d'ajouter ceci. Bien sûr, combien cela aide et quand il est préférable d'ajouter l'indice varie en raison de plusieurs facteurs, c'est donc un peu d'essai et d'erreur.
- Vous devrez peut-être mettre à jour manuellement les statistiques et / ou reconstruire l'index pour le maintenir à jour car les données de base changent assez fréquemment
- Assurez-vous de garder à l'esprit que l'index, tout en aidant le
SELECT
, nuira au UPDATE
car il s'agit d'un autre objet qui doit être mis à jour pendant cette opération, donc plus d'E / S. Cela joue à la fois en utilisant un index filtré (qui rétrécit à mesure que vous mettez à jour les lignes car moins de lignes correspondent au filtre), et en attendant un peu pour ajouter l'index (si cela ne sera pas très utile au début, alors aucune raison d'engager les E / S supplémentaires).
WAITFOR DELAY
à une demi-seconde environ, mais c'est un compromis avec la concurrence et peut-être le montant envoyé via l'envoi de journaux.Répondre à la deuxième partie: comment imprimer une sortie pendant la boucle.
J'ai quelques procédures de maintenance de longue durée que l'administrateur système doit parfois exécuter.
Je les exécute à partir de SSMS et j'ai également remarqué que l'
PRINT
instruction n'est affichée dans SSMS qu'après la fin de la procédure.Donc, j'utilise
RAISERROR
avec une faible gravité:J'utilise SQL Server 2008 Standard et SSMS 2012 (11.0.3128.0). Voici un exemple de travail complet à exécuter dans SSMS:
Lorsque je commente
RAISERROR
et ne laisse quePRINT
les messages dans l'onglet Messages de SSMS, ils n'apparaissent qu'après la fin du lot, au bout de 6 secondes.Lorsque je commente
PRINT
et utiliseRAISERROR
les messages de l'onglet Messages de SSMS, ils apparaissent sans attendre 6 secondes, mais au fur et à mesure que la boucle progresse.Fait intéressant, lorsque j'utilise les deux
RAISERROR
etPRINT
, je vois les deux messages. D'abord vient le message du premierRAISERROR
, puis attendez 2 secondes, puis le premierPRINT
et le deuxièmeRAISERROR
, et ainsi de suite.Dans d'autres cas, j'utilise une
log
table dédiée distincte et j'insère simplement une ligne dans la table avec des informations décrivant l'état actuel et l'horodatage du processus de longue durée.Pendant que le long processus s'exécute, je périodiquement
SELECT
de lalog
table pour voir ce qui se passe.Cela a évidemment certains frais généraux, mais il laisse un journal (ou historique des journaux) que je peux examiner à mon propre rythme plus tard.
la source
Vous pouvez le surveiller à partir d'une autre connexion avec quelque chose comme:
pour voir combien il reste à faire. Cela peut être utile si une application appelle le processus, plutôt que si vous l'exécutez manuellement dans SSMS ou similaire, et doit afficher la progression: exécutez le processus principal de manière asynchrone (ou sur un autre thread), puis bouclez en appelant le "combien il reste "vérifier de temps en temps jusqu'à ce que l'appel asynchrone (ou thread) se termine.
La définition du niveau d'isolement le plus laxiste possible signifie que cela devrait revenir dans un délai raisonnable sans être bloqué derrière la transaction principale en raison de problèmes de verrouillage. Cela pourrait signifier que la valeur retournée est un peu inexacte bien sûr, mais en tant que simple indicateur de progression, cela ne devrait pas avoir d'importance du tout.
la source