解决办法" />
当前位置: 代码迷 >> Sql Server >> 解决办法
  详细解决方案

解决办法

热度:55   发布时间:2016-04-24 09:28:24.0
一次意外的X锁不阻塞问题

    最近有一个朋友问我一个关于给查询操作强制上X锁却不阻塞的问题。该查询写在一个存储过程中,代码如代码1所示:

   1: create PROC [dbo].[GetCityOrders]
   2:     @city NVARCHAR(10) ,
   3:     @num INT
   4: AS
   5:     SET NOCOUNT ON
   6:  
   7:     BEGIN TRY
   8:  
   9:         BEGIN TRAN
  10:  
  11:         SELECT TOP ( @num )
  12:                 id ,
  13:                 number ,
  14:                 price ,
  15:                 mid ,
  16:                 @city city
  17:         INTO    #cityorders
  18:         FROM    cmcc WITH ( XLOCK )
  19:         WHERE   prov = 0
  20:                 AND status = 0
  21:                 AND city = @city
  22:  
  23:         UPDATE  cmcc
  24:         SET     status = 100
  25:         WHERE   id IN ( SELECT  id
  26:                         FROM    #cityorders )
  27:  
  28:         SELECT  o.* ,
  29:                 c.attach
  30:         FROM    #cityorders o
  31:                 LEFT JOIN cmcc_attach c ON o.id = c.id
  32:  
  33:         DROP TABLE #cityorders
  34:  
  35:         COMMIT TRAN
  36:  
  37:     END TRY
  38:     BEGIN CATCH
  39:  
  40:         ROLLBACK
  41:  
  42:     END CATCH

代码1.

 

    该存储过程首先通过对查询操作加X锁,使得其他读取操作更新时不影响该部分加X锁的操作。乍一看没有任何问题,但是当业务上线后就发现,即使查询有了X锁,但实际上还是会有多个调用该存储过程的客户端同时读取到同一条数据的现象现象。

 

原因?

    为了验证原因,我们来做一个Demo测试,首先我们创建测试表,代码如代码2所示。

   1: CREATE TABLE dbo.DemoX
   2:     (
   3:       [key] INT PRIMARY KEY ,
   4:       [value] INT,
   5:     );
   6: GO
   7: INSERT  INTO dbo.DemoX
   8:         ( [key], value )
   9: VALUES  ( 1, 100 );
  10: GO

代码2.创建测试DEMO

 

    接下来,对该DemoX表进行Select操作,并查看锁。如代码3所示。

   1: BEGIN TRAN
   2: SELECT  [key],value
   3: FROM    dbo.DemoX D WITH (XLOCK);
   4:  
   5: SELECT  L.resource_type,
   6:         L.request_mode,
   7:         L.request_status,
   8:         L.resource_description,
   9:         L.resource_associated_entity_id
  10: FROM    sys.dm_tran_current_transaction T
  11: JOIN    sys.dm_tran_locks L
  12:         ON  L.request_owner_id = T.transaction_id;

代码3.使用X锁提示查语句

 

    在代码3中显式指定了X锁,并查看上锁情况,可以看出X锁以及对应父对象上的意向锁都正常存在,如图1所示。

image

图1.

 

      我们再开另外一个窗口运行一个普通的Select,结果如图2所示。

image

图2.

 

为什么没有阻塞

    理论上来说,第二个查询应该会被阻塞,因为第二个查询所需加的S锁和第一个查询的X锁不兼容。后来在网上找打StackOverFlow的一篇博文:“http://stackoverflow.com/questions/4609217/sql-server-the-misleading-xlock-optimizations”,找到了答案。

    在SQL Server中,默认的已提交读为了保证不读脏数据(既在内存中修改,还未落盘的数据),会对需要查找的数据上S锁,但如果发现数据并不是脏数据,则会优化跳过加S锁的步骤,代码3中的查询语句强制使用了X锁提示,但未进行任何数据修改,所以不存在脏数据,因此后续查询就通过优化放弃使用S锁,从而不阻塞,导致了意料之外的结果。

 

解决办法

   SQL Server对于该特性的优化仅仅对行锁生效,如果在指定查询时使用页锁提示,则会按照语句,对阻塞后续查询,代码如代码4所示。

   1: SELECT  [key],value
   2: FROM    dbo.DemoX D WITH (PAGLOCK,XLOCK);

代码4.

    但显而易见,该方法会降低并发,如果有可能,请不要对Select操作使用X锁提示,否则请加上页锁提示。

    另一个办法是使用CTE进行表更新,将代码1中的代码两部分合二为一,数据在更新时会导致脏数据,因此不会出现跳过S锁的情况。

6楼桦仔
应该用merge语句或CTE
Re: CareySon
@桦仔,yes
5楼我是大菠萝
@宋桑 由于这个业务每次都是调用存储过程实现功能,即便是大并发环境,相邻进程的事务都是先申请xlock,也不会出现文中描述的S锁不被X锁阻塞的情况啊
Re: wy123
@我是大菠萝,他这个读是没有以排他(xlock)的方式去读,,仅仅普通的select,刚好绕过那个规则了(读取数据时因为某些条件,跳过了加S锁的步骤),,,您写的那篇读的时候是以xlock的方式去读的,当另外一个会话以xlock的方式去读,事物未提交之前,再以xlock的方式去读,就被阻塞了,,如果(以多线程)去执行select+xlock的存储过程,不同会话之间的读,当然会被阻塞,他这里表达的是select+xlock不会阻塞select,,您的测试证明select+xlock会阻塞select+xlock,说明排他所保持到事物结束,也没有问题
Re: CareySon
@我是大菠萝,如@wy123所述,是因为优化器跳过加S锁的步骤。我个人认为这应该属于BUG
4楼Antineutrino
什么情况下需要在没有脏数据的时候还要加锁呢?
Re: Shao明
@Antineutrino,其实一些特殊的行业都是那样的,数据只能展现在一个客服端
3楼wy123
另外这种情况是不是也是因为楼主后面说的规则的原因呢,(读的时候发现非脏读,就不加S锁),,drop table tUpdatecreate table tUpdate(id int primary key,name varchar(100))insert into tUpdate values (1,#39;AAA#39;)begin tran--更新值与原值相同update tUpdate set name=#39;AAA#39; where id=1;--先不提交--再开一个会话执行select * from tUpdate where id=1;--正常查询到结果
Re: CareySon
@wy123,是的,其实可以用Trace或扩展事件跟踪下就一目了然了。
2楼ShanksGao
有内容,赞下
Re: CareySon
@ShanksGao,多谢高兄
1楼桦仔英语学习
By design 的,https://social.msdn.microsoft.com/Forums/sqlserver/en-US/b1138478-730a-45f9-b4b5-6736dca3c747/pessimistic-locking?forum=transactsql,,Using XLOCK in SELECT statements will not prevent reads from happening. This is because SQL Server has a special optimization under read committed isolation level that checks if the row is dirty or not and ignores the xlock if the row has not changed. Since this is acceptable under the read committed isolation level semantics it is by design. So you will have to use a more aggressive locking hint like UPDLOCK with ROWLOCK.
Re: CareySon
@桦仔英语学习,但在这个particular场景下算是BUG。。,PS:你的名字什么情况
  相关解决方案