T-SQL est une sous requête pour une restriction de mise à jour atomique avec la mise à jour?

StackOverflow https://stackoverflow.com/questions/4097126

Question

J'ai une implémentation simple de file d'attente dans MS Sql Server 2008 R2. Voici la Essense de la file d'attente:

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
)

Je veux choisir atomiquement les n lignes ordonnées par la priorité et l'identifiant où IsBeingProcessed est fausse et mettre à jour ces lignes à dire qu'ils sont en cours de traitement. Je pensais utiliser une combinaison de mise à jour, Top, sortie et par ordre, mais malheureusement, vous ne pouvez pas utiliser le dessus et par ordre dans une déclaration de mise à jour.

Je l'ai fait une à la clause pour limiter la mise à jour et que sous requête ne l'ordre par (voir ci-dessous). Ma question est, est cette déclaration tout atomique, ou ai-je besoin de l'envelopper dans une transaction?

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

Voici quelques sql pour insérer quelques lignes factices:

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'');
Était-ce utile?

La solution

Si je comprends la motivation de la question que vous voulez éviter la possibilité que deux transactions simultanées pourraient aussi bien exécuter la sous requête pour obtenir les lignes de N supérieur pour traiter ensuite procéder à mettre à jour les mêmes lignes?

Dans ce cas, j'utiliser cette approche.

;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  

J'étais un peu plus tôt certain que SQL Server prendrait des verrous de U lors du traitement de votre version avec la sous requête bloquant ainsi deux transactions simultanées à la lecture des mêmes lignes de TOP N. Cela ne semble pas être le cas.

Test de 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 (Exécuter en 2 sessions simultanées 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

Presque immédiatement l'exécution est interrompue puisque les deux opérations simultanées sont mis à jour la même ligne de sorte que les serrures de S prises tout en identifiant le TOP 1 priority doivent obtenir leur libération avant qu'elle acquiert un verrou de U alors les 2 opérations se déroulent pour obtenir le U de ligne et de verrouillage de X en séquence.

Tas

Si un CI est ajouté ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority) alors blocage se produit presque immédiatement au lieu que dans ce cas la serrure rangée de S ne soit libéré pas, une transaction aquires un verrou de U sur la ligne et attend pour le convertir en un verrou de X et l'autre transaction attend toujours de convertir son verrou de S un verrou de U.

index cluster

Si la requête ci-dessus est modifié pour utiliser MIN plutôt que TOP

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

Ensuite, SQL Server parvient à éliminer complètement la sous requête du plan et prend U bloque tout le chemin.

entrer image description ici

Autres conseils

Chaque instruction T-SQL individuel est, selon toute mon expérience et tous les Documenation que j'ai jamais lu, censé être atomique. Ce que vous avez il y a une seule instruction T-SQL, ergo est devrait être atomique et ne nécessitera pas de déclarations de transactions explicites. Je l'ai utilisé ce genre précis de la logique à plusieurs reprises, et n'a jamais eu un problème avec elle. Je me réjouis de voir si quelqu'un comme un avis alternatif justifiables.

Par ailleurs, regardez dans les fonctions de classement, en particulier row_number (), pour récupérer un certain nombre d'éléments. La syntaxe est peut-être un peu maladroit, mais dans l'ensemble, ils sont des outils flexibles et puissants. (Il y a environ une pile bazillion des questions et des réponses qui Overlow les traitent.)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top