Domanda

Ho una semplice implementazione di coda in MS SQL Server 2008 R2. Ecco l'essenza della coda:

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
)

voglio selezionare atomicamente all'inizio n righe in ordine di priorità e l'id dove IsBeingProcessed è falsa e aggiornare le righe per dire che sono trattati. Ho pensato di utilizzare una combinazione di aggiornamento, Top, uscita e ORDER BY, ma purtroppo non è possibile utilizzare parte superiore e l'ordine da parte in una dichiarazione di aggiornamento.

Così ho fatto un clausola per limitare l'aggiornamento e che lo fa domanda secondaria dell'ordine da parte (vedi sotto). La mia domanda è, è tutta questa affermazione atomica, o ho bisogno di avvolgerlo in una transazione?

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

Ecco qualche SQL per inserire alcune righe fittizi:

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'');
È stato utile?

Soluzione

Se ho capito la motivazione per la domanda si vuole evitare la possibilità che due transazioni concorrenti potrebbero sia eseguire la query sub per ottenere le righe in alto N per elaborare quindi procedere per aggiornare le stesse righe?

In questo caso userei questo approccio.

;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  

ero un po 'prima incerto se SQL Server avrebbe preso serrature U durante l'elaborazione la versione con la query sub bloccando così due transazioni concorrenti dalla lettura delle stesse righe TOP N. Questo non sembra essere il caso.

Test Table

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

Test Script (Esegui in 2 sessioni simultanee SSMS)

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

Quasi immediatamente esecuzione interrompe sia come transazioni simultanee aggiornare la stessa riga in modo serrature S adottate pur identificando il TOP 1 priority devono ottenere rilasciato prima che acquisisce una serratura U poi le 2 operazioni procedere per ottenere il U riga e serratura X in sequenza.

Mucchio

Se un CI viene aggiunto ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority) quindi deadlock si verifica quasi immediatamente, invece, come in questo caso il blocco di riga S non viene rilasciato, una transazione acquisisce un blocco U sulla riga e attese per convertirlo in un blocco e l'altro X operazione è ancora in attesa di convertire il suo blocco S ad un blocco U.

Clustered Index

Se la query di cui sopra è cambiato in uso MIN piuttosto che TOP

WHERE priority = (SELECT MIN(priority)
                   FROM JobsToProcess 
                   WHERE isprocessed=0 
                   )

Poi SQL Server riesce ad eliminare completamente la query sub dal piano e prende U blocca tutta la strada.

entrare descrizione dell'immagine qui

Altri suggerimenti

Ogni singola istruzione T-SQL è, secondo tutta la mia esperienza e tutta la documenation che abbia mai letto, dovrebbe essere atomica. Quello che ci hai è una singola istruzione T-SQL, ergo è dovrebbe essere atomica e non richiede dichiarazioni transazione esplicita. Ho usato questo preciso tipo di logica molte volte, e mai avuto un problema con esso. Non vedo l'ora di vedere se qualcuno come un parere alternativo supportabile.

Per inciso, sguardo nelle funzioni ranking, in particolare row_number (), per il recupero di un determinato numero di elementi. La sintassi è forse un po 'imbarazzante, ma nel complesso si tratta di strumenti flessibili e potenti. (Ci sono circa un bazillion domande Pila Overlow e risposte che li trattano.)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top