sql-server – T-SQL是更新限制Atomic的子查询吗?

前端之家收集整理的这篇文章主要介绍了sql-server – T-SQL是更新限制Atomic的子查询吗?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我在MS sql Server 2008 R2中有一个简单的队列实现.这是队列的本质:
  1. CREATE TABLE ToBeProcessed
  2. (
  3. 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
  4. )

我想原子地选择按优先级排序的前n行和IsBeingProcessed为false的id,并更新这些行以表示它们正在被处理.我以为我会使用Update,Top,Output和Order By的组合,但不幸的是你不能在Update语句中使用top和order by.

所以我创建了一个in子句来限制更新,并且子查询按顺序执行(见下文).我的问题是,这整个语句是原子的,还是我需要将它包装在一个事务中?

  1. DECLARE @numberToProcess INT = 2
  2.  
  3. CREATE TABLE #IdsToProcess
  4. (
  5. Id BIGINT NOT null
  6. )
  7.  
  8. UPDATE
  9. ToBeProcessed
  10. SET
  11. ToBeProcessed.IsBeingProcessed = 1
  12. OUTPUT
  13. INSERTED.Id
  14. INTO
  15. #IdsToProcess
  16. WHERE
  17. ToBeProcessed.Id IN
  18. (
  19. SELECT TOP(@numberToProcess)
  20. ToBeProcessed.Id
  21. FROM
  22. ToBeProcessed
  23. WHERE
  24. ToBeProcessed.IsBeingProcessed = 0
  25. ORDER BY
  26. ToBeProcessed.Id,ToBeProcessed.Priority DESC)
  27.  
  28. SELECT
  29. *
  30. FROM
  31. #IdsToProcess
  32.  
  33. DROP TABLE #IdsToProcess

这是一些插入一些虚拟行的sql

  1. INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
  2. INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
  3. INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
  4. INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
  5. INSERT INTO ToBeProcessed (SomeData) VALUES (N'');

解决方法

如果我理解问题的动机,你想避免两个并发事务都可以执行子查询以获得前N行进行处理然后继续更新相同行的可能性?

在那种情况下,我会使用这种方法.

  1. ;WITH cte As
  2. (
  3. SELECT TOP(@numberToProcess)
  4. *
  5. FROM
  6. ToBeProcessed WITH(UPDLOCK,ROWLOCK,READPAST)
  7. WHERE
  8. ToBeProcessed.IsBeingProcessed = 0
  9. ORDER BY
  10. ToBeProcessed.Id,ToBeProcessed.Priority DESC
  11. )
  12. UPDATE
  13. cte
  14. SET
  15. IsBeingProcessed = 1
  16. OUTPUT
  17. INSERTED.Id
  18. INTO
  19. #IdsToProcess

我之前有点不确定sql Server是否会在使用子查询处理您的版本时使用U锁,从而阻止两个并发事务读取相同的TOP N行.这似乎不是这种情况.

测试表

  1. CREATE TABLE JobsToProcess
  2. (
  3. priority INT IDENTITY(1,1),isprocessed BIT,number INT
  4. )
  5.  
  6. INSERT INTO JobsToProcess
  7. SELECT TOP (1000000) 0,0
  8. FROM master..spt_values v1,master..spt_values v2

测试脚本(在2个并发SSMS会话中运行)

  1. BEGIN TRY
  2. DECLARE @FinishedMessage VARBINARY (128) = CAST('TestFinished' AS VARBINARY (128))
  3. DECLARE @SynchMessage VARBINARY (128) = CAST('TestSynchronising' AS VARBINARY (128))
  4. SET CONTEXT_INFO @SynchMessage
  5.  
  6. DECLARE @OtherSpid int
  7.  
  8. WHILE(@OtherSpid IS NULL)
  9. SELECT @OtherSpid=spid
  10. FROM sys.sysprocesses
  11. WHERE context_info=@SynchMessage and spid<>@@SPID
  12.  
  13. SELECT @OtherSpid
  14.  
  15.  
  16. DECLARE @increment INT = @@spid
  17. DECLARE @number INT = @increment
  18.  
  19. WHILE (@number = @increment AND NOT EXISTS(SELECT * FROM sys.sysprocesses WHERE context_info=@FinishedMessage))
  20. UPDATE JobsToProcess
  21. SET @number=number +=@increment,isprocessed=1
  22. WHERE priority = (SELECT TOP 1 priority
  23. FROM JobsToProcess
  24. WHERE isprocessed=0
  25. ORDER BY priority DESC)
  26.  
  27. SELECT *
  28. FROM JobsToProcess
  29. WHERE number not in (0,@OtherSpid,@@spid)
  30. SET CONTEXT_INFO @FinishedMessage
  31. END TRY
  32. BEGIN CATCH
  33. SET CONTEXT_INFO @FinishedMessage
  34. SELECT ERROR_MESSAGE(),ERROR_NUMBER()
  35. END CATCH

几乎立即执行停止,因为两个并发事务都更新同一行,因此在识别TOP 1优先级时采用的S锁必须在获取U锁之前释放,然后2个事务继续按顺序获得行U和X锁.

如果添加了CI ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED(优先级),那么死锁几乎立即发生,因为在这种情况下,行S锁没有被释放,一个事务获取行上的U锁并等待将其转换为一个X锁,另一个事务仍在等待将其S锁转换为U锁.

如果上面的查询更改为使用MIN而不是TOP

  1. WHERE priority = (SELECT MIN(priority)
  2. FROM JobsToProcess
  3. WHERE isprocessed=0
  4. )

然后sql Server设法完全消除计划中的子查询并一直采用U锁.

猜你在找的MsSQL相关文章