T-SQL es una consulta sub para una restricción de actualización Atómica con la actualización?
-
28-09-2019 - |
Pregunta
Tengo una sencilla aplicación de cola en MS SQL Server 2008 R2. Aquí está la esencia de la cola:
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
)
Quiero seleccionar atómicamente la parte superior n filas ordenadas por la prioridad y el id donde IsBeingProcessed es falsa y actualizar aquellas filas que decir que se están procesando. Pensé que haría uso de una combinación de actualización, principal, la impresión y el orden, pero por desgracia no se puede utilizar la parte superior y el orden en una instrucción de actualización.
Así que he hecho una en la cláusula para restringir la actualización y que consulta sub hace lo ordenado por (véase más adelante). Mi pregunta es, ¿toda esta declaración atómica, o necesito para envolverlo en una transacción?
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
Aquí hay algo de SQL para insertar algunas filas ficticias:
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'');
Solución
Si entiendo el motivo de la pregunta se quiere evitar la posibilidad de que dos transacciones simultáneas podrían ejecutar tanto la consulta sub para obtener la parte superior de N filas, para procesar a continuación, proceder a la actualización de las mismas filas?
En ese caso, me gustaría usar este enfoque.
;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
Yo era un poco más temprano incierto si SQL Server tomaría cerraduras U
al procesar su versión con la sub consulta bloqueando así dos transacciones simultáneas de la lectura de las mismas filas TOP N
. Este no parece ser el caso.
tabla de prueba
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 (Ejecutar en 2 sesiones concurrentes 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
Casi inmediatamente la ejecución se detiene como actualizar ambas operaciones simultáneas de la misma fila por lo que las cerraduras S
tomadas mientras que la identificación de la TOP 1 priority
deben ser liberado antes de que aquires una cerradura U
entonces los 2 transacciones proceden a obtener la U
fila y bloqueo X
en secuencia.
Si un IC se añade a continuación ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority)
interbloqueo se produce casi inmediatamente en cambio, como en este caso el bloqueo de registro S
no quede en libertad, una transacción aquires una cerradura U
en la fila y espera para convertirlo en un bloqueo X
y el otro transacción aún está esperando para convertir su bloqueo S
a una cerradura U
.
Si la consulta se cambia de arriba para uso MIN
en lugar de TOP
WHERE priority = (SELECT MIN(priority)
FROM JobsToProcess
WHERE isprocessed=0
)
A continuación, SQL Server administra para eliminar por completo la consulta sub del plan y toma U
encierra todo el camino.
Otros consejos
Cada instrucción T-SQL individuo es, de acuerdo con toda mi experiencia y todo el Documenation que he leído, se supone que atómica. Lo que tienes hay una sola instrucción de T-SQL, ergo es debe ser atómica y no requerirá declaraciones explícitas de transacción. He utilizado este tipo precisa de la lógica muchas veces, y nunca he tenido un problema con él. Miro adelante a ver si alguien como una opinión alternativa soportable.
A propósito, mirada en las funciones de clasificación, específicamente row_number (), para la recuperación de un número determinado de artículos. La sintaxis es quizás un poco torpe, pero en general son herramientas flexibles y potentes. (Hay alrededor de una pila bazillion preguntas y respuestas Overlow que ellos discuten.)