T-SQL ist eine Sub-Abfrage für eine Einschränkung aktualisieren Atomic mit dem Update?
-
28-09-2019 - |
Frage
Ich habe eine einfache Warteschlange Implementierung in MS SQL Server 2008 R2 bekam. Hier ist die Essenz der Warteschlange:
CREATE TABLE ToBeProcessed
(
Id BIGINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
[Priority] INT DEFAULT(100) NOT NULL,
IsBeingProcessed BIT default (0) NOT NULL,
SomeData nvarchar(MAX) NOT null
)
Ich möchte atomar die Spitze wählen n von der Priorität geordneten Reihen und die ID, wo IsBeingProcessed falsch ist und die Zeilen aktualisieren zu sagen, sie verarbeitet werden. Ich dachte, ich eine Kombination von Update, Top verwenden würde, Output und Order By aber leider kann man nicht oben und Ordnung, indem sie in einer Update-Anweisung verwendet werden.
Also habe ich eine in-Klausel zu beschränken das Update und die Sub-Abfrage funktioniert die Bestellung durch (siehe unten). Meine Frage ist, ist diese ganze Anweisung atomar, oder muss ich es in einer Transaktion wickeln?
DECLARE @numberToProcess INT = 2
CREATE TABLE #IdsToProcess
(
Id BIGINT NOT null
)
UPDATE
ToBeProcessed
SET
ToBeProcessed.IsBeingProcessed = 1
OUTPUT
INSERTED.Id
INTO
#IdsToProcess
WHERE
ToBeProcessed.Id IN
(
SELECT TOP(@numberToProcess)
ToBeProcessed.Id
FROM
ToBeProcessed
WHERE
ToBeProcessed.IsBeingProcessed = 0
ORDER BY
ToBeProcessed.Id,
ToBeProcessed.Priority DESC)
SELECT
*
FROM
#IdsToProcess
DROP TABLE #IdsToProcess
Hier einige SQL einige Dummy-Zeilen einzufügen:
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
Lösung
Wenn ich die Motivation für die Frage verstehen, wollen Sie die Möglichkeit zu vermeiden, dass zwei gleichzeitige Transaktionen sowohl die Sub-Abfrage ausführen konnten die Top-N Zeilen erhalten zu verarbeiten dann gehen die gleichen Zeilen aktualisieren?
In diesem Fall würde ich diesen Ansatz verwenden.
;WITH cte As
(
SELECT TOP(@numberToProcess)
*
FROM
ToBeProcessed WITH(UPDLOCK,ROWLOCK,READPAST)
WHERE
ToBeProcessed.IsBeingProcessed = 0
ORDER BY
ToBeProcessed.Id,
ToBeProcessed.Priority DESC
)
UPDATE
cte
SET
IsBeingProcessed = 1
OUTPUT
INSERTED.Id
INTO
#IdsToProcess
war ich etwas unsicher, ob früher SQL Server U
Sperren nehmen würde, wenn Ihre Version mit der Abfrage so Unter Verarbeitung blockiert zwei gleichzeitige Transaktionen aus den gleichen TOP N
Zeilen zu lesen. Dies scheint nicht der Fall zu sein.
Test Tabelle
CREATE TABLE JobsToProcess
(
priority INT IDENTITY(1,1),
isprocessed BIT ,
number INT
)
INSERT INTO JobsToProcess
SELECT TOP (1000000) 0,0
FROM master..spt_values v1, master..spt_values v2
Testskript (Run in 2 gleichzeitigen SSMS Sitzungen)
BEGIN TRY
DECLARE @FinishedMessage VARBINARY (128) = CAST('TestFinished' AS VARBINARY (128))
DECLARE @SynchMessage VARBINARY (128) = CAST('TestSynchronising' AS VARBINARY (128))
SET CONTEXT_INFO @SynchMessage
DECLARE @OtherSpid int
WHILE(@OtherSpid IS NULL)
SELECT @OtherSpid=spid
FROM sys.sysprocesses
WHERE context_info=@SynchMessage and spid<>@@SPID
SELECT @OtherSpid
DECLARE @increment INT = @@spid
DECLARE @number INT = @increment
WHILE (@number = @increment AND NOT EXISTS(SELECT * FROM sys.sysprocesses WHERE context_info=@FinishedMessage))
UPDATE JobsToProcess
SET @number=number +=@increment,isprocessed=1
WHERE priority = (SELECT TOP 1 priority
FROM JobsToProcess
WHERE isprocessed=0
ORDER BY priority DESC)
SELECT *
FROM JobsToProcess
WHERE number not in (0,@OtherSpid,@@spid)
SET CONTEXT_INFO @FinishedMessage
END TRY
BEGIN CATCH
SET CONTEXT_INFO @FinishedMessage
SELECT ERROR_MESSAGE(), ERROR_NUMBER()
END CATCH
Fast sofort stoppt die Ausführung, da beide gleichzeitig ablaufenden Transaktionen die gleiche Zeile aktualisieren, so dass die S
Schlösser genommen, während der TOP 1 priority
identifizieren müssen frei bekommen, bevor es eine U
Sperre erwirbt dann die 2-Transaktionen gehen die Zeilen U
und X
Sperre in Folge zu erhalten.
Wenn ein CI hinzugefügt wird ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority)
dann Deadlock fast sofort statt, wie in diesem Fall tritt die Reihe S
Sperre nicht frei bekommen, eine Transaktion erwirbt eine U
Sperre für die Zeile und wartet es auf eine X
Sperre zu konvertieren und die anderen Transaktion wartet noch seine S
Sperre auf eine U
Sperre zu konvertieren.
Wenn die Abfrage oben Verwendung MIN
geändert wird, statt TOP
WHERE priority = (SELECT MIN(priority)
FROM JobsToProcess
WHERE isprocessed=0
)
Dann SQL Server verwaltet vollständig die Sub-Abfrage aus dem Plan zu beseitigen und nimmt U
sperrt den ganzen Weg.
Andere Tipps
Jede einzelne T-SQL-Anweisung, nach allem meiner Erfahrung und all dem documenation ich je gelesen habe, sollte atomar sein. Was haben Sie da eine einzelne T-SQL-Anweisung ist, ist ergo sollte atomar sein und erfordert keine explizite Transaktionsanweisungen. Ich habe diese genaue Art von Logik oft verwendet, und hatte nie ein Problem mit ihm. Ich freue mich, wenn jemand als erträglich alternative Meinung zu sehen.
Im übrigen Blick in die Ranking-Funktionen, speziell row_number () für eine festgelegte Anzahl von Elementen abgerufen werden. Die Syntax ist vielleicht ein bisschen umständlich, aber insgesamt sind sie flexibel und leistungsfähige Werkzeuge. (Es gibt etwa eine bazillion Stapel Overlow Fragen und Antworten, die sie diskutieren.)