Ich bin weder ein großer Fan des zusätzlichen "Lock" -Tisches noch der Idee, den gesamten Tisch zu sperren, um die nächste Platte zu holen. Ich verstehe, warum dies getan wird, aber das schadet auch der Parallelität für Vorgänge, die aktualisiert werden, um einen gesperrten Datensatz freizugeben (sicherlich können zwei Prozesse nicht darüber streiten, wenn es nicht möglich ist, dass zwei Prozesse denselben Datensatz am gesperrt haben gleiche Zeit).
Ich würde es vorziehen, der Tabelle eine ProcessStatusID-Spalte (normalerweise TINYINT) hinzuzufügen, in der die Daten verarbeitet werden. Und gibt es ein Feld für LastModifiedDate? Wenn nicht, sollte es hinzugefügt werden. Wenn ja, werden diese Datensätze dann außerhalb dieser Verarbeitung aktualisiert? Wenn Datensätze außerhalb dieses bestimmten Prozesses aktualisiert werden können, sollte ein weiteres Feld hinzugefügt werden, um StatusModifiedDate (oder ähnliches) zu verfolgen. Für den Rest dieser Antwort werde ich nur "StatusModifiedDate" verwenden, da dies in seiner Bedeutung klar ist (und tatsächlich als Feldname verwendet werden kann, selbst wenn derzeit kein "LastModifiedDate" -Feld vorhanden ist).
Die Werte für ProcessStatusID (die in eine neue Nachschlagetabelle mit dem Namen "ProcessStatus" und Fremdschlüssel für diese Tabelle eingefügt werden sollten) können sein:
- Abgeschlossen (oder in diesem Fall sogar "Ausstehend", da beide "zur Verarbeitung bereit" bedeuten)
- In Bearbeitung (oder "Verarbeitung")
- Fehler (oder "WTF?")
An diesem Punkt scheint es sicher anzunehmen, dass die Anwendung nur den nächsten zu verarbeitenden Datensatz abrufen möchte und nichts weitergibt, um diese Entscheidung zu treffen. Wir möchten also den ältesten Datensatz (zumindest in Bezug auf StatusModifiedDate) abrufen, der auf "Abgeschlossen" / "Ausstehend" gesetzt ist. Etwas in der Art von:
SELECT TOP 1 pt.RecordID
FROM ProcessTable pt
WHERE pt.StatusID = 1
ORDER BY pt.StatusModifiedDate ASC;
Wir möchten diesen Datensatz gleichzeitig auf "In Bearbeitung" aktualisieren, um zu verhindern, dass der andere Prozess ihn abruft. Wir könnten die OUTPUT
Klausel verwenden, um UPDATE und SELECT in derselben Transaktion ausführen zu lassen:
UPDATE TOP (1) pt
SET pt.StatusID = 2,
pt.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
OUTPUT INSERTED.RecordID
FROM ProcessTable pt
WHERE pt.StatusID = 1;
Das Hauptproblem hierbei ist, dass wir zwar eine Operation ausführen können, es jedoch keine Möglichkeit gibt, TOP (1)
eine UPDATE
Operation durchzuführen ORDER BY
. Wir können es jedoch in einen CTE einwickeln, um diese beiden Konzepte zu kombinieren:
;WITH cte AS
(
SELECT TOP 1 pt.RecordID
FROM ProcessTable pt (READPAST, ROWLOCK, UPDLOCK)
WHERE pt.StatusID = 1
ORDER BY pt.StatusModifiedDate ASC;
)
UPDATE cte
SET cte.StatusID = 2,
cte.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
OUTPUT INSERTED.RecordID;
Die offensichtliche Frage ist, ob zwei Prozesse, die gleichzeitig SELECT ausführen, denselben Datensatz abrufen können. Ich bin mir ziemlich sicher, dass die UPDATE with OUTPUT-Klausel, insbesondere in Kombination mit den READPAST- und UPDLOCK-Hinweisen (siehe unten für weitere Details), in Ordnung sein wird. Ich habe dieses genaue Szenario jedoch nicht getestet. Wenn sich die obige Abfrage aus irgendeinem Grund nicht um die Race-Bedingung kümmert, wird Folgendes hinzugefügt: Anwendungssperren.
Die obige CTE-Abfrage kann in sp_getapplock und sp_releaseapplock eingeschlossen werden , um einen "Gate Keeper" für den Prozess zu erstellen. Dabei kann jeweils nur ein Prozess eingegeben werden, um die obige Abfrage auszuführen. Die anderen Prozesse werden blockiert, bis der Prozess mit dem Applock sie freigibt. Und da dieser Schritt des Gesamtprozesses nur darin besteht, die RecordID abzurufen, ist er ziemlich schnell und blockiert die anderen Prozesse nicht sehr lange. Und genau wie bei der CTE-Abfrage blockieren wir nicht die gesamte Tabelle, wodurch andere Aktualisierungen für andere Zeilen zugelassen werden (um deren Status entweder auf "Abgeschlossen" oder "Fehler" zu setzen). Im Wesentlichen:
BEGIN TRANSACTION;
EXEC sp_getapplock @Resource = 'GetNextRecordToProcess', @LockMode = 'Exclusive';
{CTE UPDATE query shown above}
EXEC sp_releaseapplock @Resource = 'GetNextRecordToProcess';
COMMIT TRANSACTION;
Anwendungssperren sind sehr schön, sollten aber sparsam verwendet werden.
Zuletzt benötigen Sie nur eine gespeicherte Prozedur, um den Status auf "Abgeschlossen" oder "Fehler" zu setzen. Und das kann einfach sein:
CREATE PROCEDURE ProcessTable_SetProcessStatusID
(
@RecordID INT,
@ProcessStatusID TINYINT
)
AS
SET NOCOUNT ON;
UPDATE pt
SET pt.ProcessStatusID = @ProcessStatusID,
pt.StatusModifiedDate = GETDATE() -- or GETUTCDATE()
FROM ProcessTable pt
WHERE pt.RecordID = @RecordID;
Tabellenhinweise (gefunden unter Hinweise (Transact-SQL) - Tabelle ):