一次意外的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所示。

图1.

 

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

图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锁的情况。

时间: 2024-08-27 21:18:09

一次意外的X锁不阻塞问题的相关文章

第十六章——处理锁、阻塞和死锁(3)——使用SQLServer Profiler侦测死锁

原文:第十六章--处理锁.阻塞和死锁(3)--使用SQLServer Profiler侦测死锁 前言: 作为DBA,可能经常会遇到有同事或者客户反映经常发生死锁,影响了系统的使用.此时,你需要尽快侦测和处理这类问题. 死锁是当两个或者以上的事务互相阻塞引起的.在这种情况下两个事务会无限期地等待对方释放资源以便操作.下面是死锁的示意图: 本文将使用SQLServer Profiler来跟踪死锁. 准备工作: 为了侦测死锁,我们需要先模拟死锁.本例将使用两个不同的会话创建两个事务. 步骤: 1. 打

第十六章——处理锁、阻塞和死锁(2)——侦测阻塞和阻塞查询

原文:第十六章--处理锁.阻塞和死锁(2)--侦测阻塞和阻塞查询 前言: 如果一个事务正在等待一些给其他事务锁定的资源.这个事务就被成为"被阻塞的事务".反过来,引起阻塞的事务,也就是锁定资源并造成其他事务等待的事务叫做"正在阻塞的事务". 长时间运行事务会阻塞其他事务和查询,使他们等待长时间.在繁重的系统中,很多时候我们会遇到阻塞问题,如果一个事务因为阻塞未完成.会造成一些列的等待链. 本文将介绍如何发现并马上解决这方面的问题. 准备工作: 本例依旧使用SQLSe

第十六章——处理锁、阻塞和死锁(1)——确定长时间运行的事务

原文:第十六章--处理锁.阻塞和死锁(1)--确定长时间运行的事务 前言: 事务是OLTP系统中的主要部分.它管理数据一致性和数据并发问题,当多个资源同时被读取或者修改相同数据时,SQLServer会通过锁定机制来确保数据库中的数据总是处于一个有效状态.在SQLServer中,锁管理器是负责实现这些锁机制.SQLServer对于不同的资源类型提供不同的锁类型,如数据库.文件.对象.表.区.页和键. 当你使用事务时,依然会遇到由事务引起的问题,这些通常是由于锁.阻塞和死锁引起的. 本系列将讲解这三

Sql Server 优化----SQL语句的执行方式与锁以及阻塞的关系

阻塞原因之一是不同的Session在访问同一张表的时候因为不兼容锁的原因造成的, 当前执行的SQL语句是否被阻塞(或者死锁),不仅跟当前表上的已有的锁有关,也会跟当前执行的SQL语句的执行方式有关 简单来说,对于表的访问方式,SQL语句的执行无非是表扫描,索引扫描,(聚集索引或者非聚集索引)索引查找等等 如果SQL语句的执行方式不当或者没有合理的索引,会造成没必要的阻塞,如果逻辑控制不当,甚至造成更严重的问题,造成数据逻辑的错误 建个测试表,下面测试演示一下 create table testI

iOS 多线程 NSThread NSOperation NSOperationQueue GCD 线程锁 线程阻塞

iPhone中的线程应用并不是无节制的,官方给出的资料显示,iPhone OS下的主线程的堆栈大小是1M,第二个线程开始就是512KB,并且该值不能通过编译器开关或线程API函数来更改,只有主线程有直接修改UI的能力,所以一些数据层面可以开辟线程来操作进行,iOS线程的操作方法有NSThread NSOperation NSOperationQueue GCD: NSThread方法有 //NSThread自动 - (IBAction)didClickNSThreadAutoButtonActi

sqlserver 锁与阻塞

DDL/索引重建 会申请 Sch-M锁 with (nolock) 会申请 Sch-S锁 with (nolock)会阻塞 sch-M, 同样Sch-M也会 阻塞with (nolock) 索引重建2014后,增加 WAIT_AT_LOW_PRIORITY 参数,会检测当前是否存在阻塞事务,存在则中断当前索引重组操作. https://www.brentozar.com/archive/2015/01/testing-alter-index-rebuild-wait_at_low_priorit

PHP自带Session隐患(session文件独占锁引起阻塞)

PHP默认的会话处理器是session.save_handler = files(即文件).如果同一个客户端同时并发发送多个请求(如ajax在页面同时发送多个请求),且脚本执行时间较长,就会导致session文件阻塞,影响性能.因为对于每个请求,PHP执行session_start(),就会取得文件独占锁,只有在该请求处理结束后,才会释放独占锁.这样,同时多个请求就会引起阻塞.解决方案如下: (1)修改会话变量后,立即使用session_write_close()来保存会话数据并释放文件锁. /

查询锁谁阻塞了谁

with vw_lock AS (SELECT * FROM v$lock) select a.sid, 'is blocking', (select 'sid:'||s.sid||' object:'||do.object_name||' rowid:'|| dbms_rowid.rowid_create ( 1, ROW_WAIT_OBJ#, ROW_WAIT_FILE#, ROW_WAIT_BLOCK#, ROW_WAIT_ROW# ) ||' sql_id:'||s.sql_id fro

zbb20180929 thread 自旋锁、阻塞锁、可重入锁、悲观锁、乐观锁、读写锁、对象锁和类锁

1.自旋锁自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行.若线程依然不能获得锁,才会被挂起.使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强.因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,