Diese Frage war mir nicht bekannt, als ich die zugehörige Frage beantwortete ( Sind in dieser while-Schleife explizite Transaktionen erforderlich? ). Der Vollständigkeit halber werde ich dieses Problem hier ansprechen, da es nicht Teil meines Vorschlags in dieser verknüpften Antwort war .
Da ich vorschlage, dies über einen SQL Agent-Job zu planen (es sind immerhin 100 Millionen Zeilen), denke ich nicht, dass irgendeine Form des Sendens von Statusnachrichten an den Client (dh SSMS) ideal ist (wenn dies der Fall ist) Immer wenn andere Projekte benötigt werden, stimme ich Vladimir zu, dass die Verwendung RAISERROR('', 10, 1) WITH NOWAIT;
der richtige Weg ist.
In diesem speziellen Fall würde ich eine Statustabelle erstellen, die pro Schleife mit der Anzahl der bisher aktualisierten Zeilen aktualisiert werden kann. Und es tut nicht weh, in der aktuellen Zeit einen Herzschlag auf den Prozess zu werfen.
Vorausgesetzt, Sie möchten den Vorgang abbrechen und neu starten können, Ich bin es leid, das UPDATE der Haupttabelle mit dem UPDATE der Statustabelle in eine explizite Transaktion zu verpacken. Wenn Sie jedoch das Gefühl haben, dass die Statustabelle aufgrund des Abbruchs nicht mehr synchron ist, können Sie den aktuellen Wert leicht aktualisieren, indem Sie ihn einfach manuell mit dem aktualisieren COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
.und es gibt zwei Tabellen zum UPDATE (dh die Haupttabelle und die Statustabelle). Wir sollten eine explizite Transaktion verwenden, um diese beiden Tabellen synchron zu halten. Wir möchten jedoch nicht riskieren, eine verwaiste Transaktion zu haben, wenn Sie den Prozess bei a abbrechen Punkt, nachdem die Transaktion gestartet, aber nicht festgeschrieben wurde. Dies sollte sicher sein, solange Sie den SQL Agent-Job nicht stoppen.
Wie können Sie den Prozess stoppen, ohne ihn zu stoppen? Indem ich ihn auffordere aufzuhören :-). Ja. Indem Sie dem Prozess ein "Signal" senden (ähnlich wie kill -3
in Unix), können Sie anfordern, dass er im nächsten geeigneten Moment stoppt (dh wenn keine aktive Transaktion vorhanden ist!) Und sich selbst schön und ordentlich bereinigen lässt.
Wie können Sie in einer anderen Sitzung mit dem laufenden Prozess kommunizieren? Verwenden Sie denselben Mechanismus, den wir erstellt haben, um Ihnen den aktuellen Status mitzuteilen: die Statustabelle. Wir müssen nur eine Spalte hinzufügen, die der Prozess am Anfang jeder Schleife überprüft, damit er weiß, ob er fortfahren oder abbrechen soll. Und da dies als SQL Agent-Job geplant werden soll (alle 10 oder 20 Minuten ausgeführt), sollten wir dies auch ganz am Anfang überprüfen, da es keinen Sinn macht, eine temporäre Tabelle mit 1 Million Zeilen zu füllen, wenn der Prozess gerade läuft einen Moment später zu beenden und keine dieser Daten zu verwenden.
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;
Sie können den Status dann jederzeit mit der folgenden Abfrage überprüfen:
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;
Möchten Sie den Prozess anhalten, unabhängig davon, ob er in einem SQL Agent-Job oder sogar in SSMS auf dem Computer eines anderen ausgeführt wird? Lauf einfach:
UPDATE ht
SET ht.PauseProcess = 1
FROM dbo.HugeTable_TempStatus ts;
Möchten Sie, dass der Prozess erneut gestartet werden kann? Lauf einfach:
UPDATE ht
SET ht.PauseProcess = 0
FROM dbo.HugeTable_TempStatus ts;
AKTUALISIEREN:
Hier sind einige zusätzliche Dinge, die Sie versuchen sollten, um die Leistung dieses Vorgangs zu verbessern. Keiner hilft garantiert, aber es lohnt sich wahrscheinlich, ihn auszuprobieren. Und mit 100 Millionen zu aktualisierenden Zeilen haben Sie genügend Zeit / Gelegenheit, einige Variationen zu testen ;-).
- Fügen Sie
TOP (@UpdateRows)
der UPDATE-Abfrage hinzu, damit die oberste Zeile wie folgt aussieht:
UPDATE TOP (@UpdateRows) ht
Manchmal hilft es dem Optimierer zu wissen, wie viele Zeilen maximal betroffen sind, damit keine Zeit damit verschwendet wird, nach mehr zu suchen.
Fügen Sie der #CurrentSet
temporären Tabelle einen PRIMARY KEY hinzu . Die Idee hier ist, dem Optimierer beim JOIN zur 100-Millionen-Zeilentabelle zu helfen.
Und nur um es so anzugeben, dass es nicht mehrdeutig ist, sollte es keinen Grund geben, der #FullSet
temporären Tabelle eine PK hinzuzufügen, da es sich nur um eine einfache Warteschlangentabelle handelt, bei der die Reihenfolge irrelevant ist.
- In einigen Fällen ist es hilfreich, einen gefilterten Index hinzuzufügen, um die
SELECT
Einspeisung in die #FullSet
temporäre Tabelle zu unterstützen. Hier einige Überlegungen zum Hinzufügen eines solchen Index:
- Die WHERE-Bedingung sollte daher mit der WHERE-Bedingung Ihrer Abfrage übereinstimmen
WHERE deleted is null or deletedDate is null
- Zu Beginn des Prozesses stimmen die meisten Zeilen mit Ihrer WHERE-Bedingung überein, sodass ein Index nicht so hilfreich ist. Möglicherweise möchten Sie warten, bis die 50% -Marke erreicht ist, bevor Sie diese hinzufügen. Wie viel es hilft und wann es am besten ist, den Index hinzuzufügen, hängt natürlich von mehreren Faktoren ab. Es ist also ein bisschen Versuch und Irrtum.
- Möglicherweise müssen Sie STATS manuell AKTUALISIEREN und / oder den Index neu erstellen, um ihn auf dem neuesten Stand zu halten, da sich die Basisdaten häufig ändern
- Denken Sie daran, dass der Index, während er dem hilft
SELECT
, das verletzt, UPDATE
da es sich um ein anderes Objekt handelt, das während dieses Vorgangs aktualisiert werden muss, daher mehr E / A. Dies funktioniert sowohl bei der Verwendung eines gefilterten Index (der beim Aktualisieren von Zeilen kleiner wird, da weniger Zeilen mit dem Filter übereinstimmen) als auch beim Warten auf das Hinzufügen des Index (wenn dies am Anfang nicht besonders hilfreich sein wird, besteht kein Grund, dies zu tun die zusätzliche E / A).
WAITFOR DELAY
auf etwa eine halbe Sekunde reduzieren. Dies ist jedoch ein Kompromiss zwischen Parallelität und möglicherweise der Überweisung per Protokollversand.Beantwortung des zweiten Teils: Drucken einer Ausgabe während der Schleife.
Ich habe einige lang laufende Wartungsverfahren, die der Systemadministrator manchmal ausführen muss.
Ich habe sie von SSMS ausgeführt und festgestellt, dass die
PRINT
Anweisung in SSMS erst angezeigt wird, nachdem die gesamte Prozedur abgeschlossen ist.Also benutze ich
RAISERROR
mit geringem Schweregrad:Ich verwende SQL Server 2008 Standard und SSMS 2012 (11.0.3128.0). Hier ist ein vollständiges Arbeitsbeispiel für die Ausführung in SSMS:
Wenn ich auskommentiere
RAISERROR
und nurPRINT
die Nachrichten auf der Registerkarte Nachrichten in SSMS hinterlasse, werden sie nach 6 Sekunden erst nach Abschluss des gesamten Stapels angezeigt.Wenn ich die Nachrichten auf der Registerkarte Nachrichten in SSMS auskommentiere
PRINT
und verwende, werden sieRAISERROR
angezeigt, ohne 6 Sekunden zu warten, aber während die Schleife fortschreitet.Interessanterweise sehe ich beide Nachrichten , wenn ich beide
RAISERROR
und verwendePRINT
. Zuerst kommt die Nachricht von zuerstRAISERROR
, dann Verzögerung für 2 Sekunden, dann zuerstPRINT
und zweitensRAISERROR
und so weiter.In anderen Fällen verwende ich eine separate dedizierte
log
Tabelle und füge einfach eine Zeile mit einigen Informationen in die Tabelle ein, die den aktuellen Status und den Zeitstempel des lang laufenden Prozesses beschreiben.Während der lange Prozess läuft ich regelmäßig
SELECT
von derlog
Tabelle, um zu sehen, was los ist.Dies hat natürlich einen gewissen Overhead, hinterlässt jedoch ein Protokoll (oder einen Protokollverlauf), das ich später in meinem eigenen Tempo untersuchen kann.
quelle
Sie können es von einer anderen Verbindung aus überwachen, indem Sie Folgendes tun:
um zu sehen, wie viel noch zu tun ist. Dies kann nützlich sein, wenn eine Anwendung den Prozess aufruft, anstatt ihn manuell in SSMS oder ähnlichem auszuführen, und den Fortschritt anzeigen muss: Führen Sie den Hauptprozess asynchron (oder in einem anderen Thread) aus und rufen Sie dann in einer Schleife auf, wie viel noch übrig ist "Überprüfen Sie jede Zeit, bis der asynchrone Aufruf (oder Thread) abgeschlossen ist.
Wenn Sie die Isolationsstufe so locker wie möglich einstellen, sollte diese in angemessener Zeit zurückkehren, ohne aufgrund von Sperrproblemen hinter der Haupttransaktion hängen zu bleiben. Dies könnte natürlich bedeuten, dass der zurückgegebene Wert etwas ungenau ist, aber als einfache Fortschrittsanzeige sollte dies überhaupt keine Rolle spielen.
quelle