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'');
War es hilfreich?

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.

Haufen

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.

Clustered Index

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.

eingeben Bild Beschreibung hier

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.)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top