T-SQL est une sous requête pour une restriction de mise à jour atomique avec la mise à jour?
-
28-09-2019 - |
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'');
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.
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
.
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.
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.)