一直没有认真了解UPDATE操作的锁。近期在MSDN论坛上看到一个问题,询问堆表更新的死锁问题,问题非常easy,有相似这种表及数据:

CREATE TABLE dbo.tb(

c1 int,

c2 char(10),

c3 varchar(10)

);

GO

DECLARE @id int;

SET @id = 0;

WHILE @id <5

BEGIN;

SET @id = @id + 1;

INSERT dbo.tb VALUES( @id, 'b' + RIGHT(10000 + @id, 4), 'c' + RIGHT(100000 + @id, 4) );

END;

在查询一中运行更新操作:

BEGIN TRAN

UPDATE dbo.tb SET c2 = 'xx' WHERE c1 = 2;

WAITFOR DELAY '00:00:30';

UPDATE dbo.tb SET c2 = 'xx' WHERE c1 = 5;

ROLLBACK;

在查询一运行開始后,立即在查询二中运行以下的操作

BEGIN TRAN

UPDATE dbo.tb SET c2 = 'xx' WHERE c1 = 1;

ROLLBACK;

为什么会出现死锁,假设条件改为 c1 = 4 则不会死锁。

開始的时候想得比較简单,死锁的表现是形成循环等待(对于两个查询而言,能够简单地觉得就是在相互等待对方锁定资源的释放)。

对于这个样例而言。第一个查询更新两次,会先更新并锁定一条记录,然后等待第二个更新。但第二个查询仅仅会更新一条记录。它要么与第一个查询冲突,无法获得锁。须要等待查询一完毕,这个时候它并没有锁定什么;要么能够获得锁,完毕更新。

似乎不应该会出现死锁,死锁会不会是其它原因导致。

在自己的电脑上简单測试了一下。似乎也确实没有死锁。

但后面通过Profile跟踪更新操作的下锁情况才发现。自己的分析大错特错了。

主要原因在于没有正确理解更新操作是怎样用锁的。

在联机帮助上“锁模式”中有关于更新的U(更新锁)和X(排它锁)的说明

http://msdn.microsoft.com/zh-cn/library/ms175519(v=sql.105).aspx

只是说得确实挺模糊的。里面还提到了S锁。我一直以为是查询数据过程中用的S锁(也 SELECT 一样)。找到满足条件的记录后用U锁,再转换为X锁做更新。

Profile(事件探查器)跟踪的结果让我知道了这是一个错误的理解,在Profile中新建一个跟踪,选择Locks中的Lock:Acquired (加锁),Lock:Acquired(释放锁)解两个事件,在筛选中设置仅仅跟踪測试用的查询窗体相应的spid(能够运行 PRINT @@SPID 获得),然后运行一个更新语句。比方 UPDATE dbo.tb SET c2 = 'xx' WHERE c1 = 3

在Profile中能够看到。对于每条记录都有加 U 锁的操作,对于不满足条件的记录,会立即释放U锁;对于满足条件的记录,终于转换为X锁。例如以下图所看到的。

注意一下,在这个跟踪结果里面。并没有出现S锁。

另外学做了一些測试:

  1. 通过加大记录量做更新測试,会发现数据扫描涉及的记录都有U锁,并不限于更新记录所在的页。这从还有一个角度说明了大表中Scan 可怕。

  2. 当使用索引Scan的时候,也会通过跟踪发现所Scan的索引资源有U锁,假设更新不涉及索引变化。那以仅仅会相应的记录有U转X锁。索引的U锁会释放;假设影响索引,那么索引的U锁会转X锁。

  3. 删除操作与更新操作相似

  4. 使用 UPDATE aSET c2 = 'xx' FROM dbo.tb AS a WITH(NOLOCK) WHERE c1 = 3  的加锁情况是一样的。 并不会由于NOLOCK的提示而不加 U 或者 X 锁


最后回头研究一下演示样例中的死锁问题:

  • 对于查询一,第一个更新依次扫描表中全部记录,对于每条记录,加 U 锁,推断是否符合更新条件。假设符合,转换为 X 锁;假设不符合条件。释放 U 锁。第一个更新完毕的时候,查询一锁定了一条记录(由于事务未完毕,所以锁一直保持),然后等待第二个更新

  • 对于查询二,依次扫描表中的每条记录(与前面的更新一样),假设它更新的记录在查询一更新的记录前被扫描到,那么这条记录也会变成 X 锁;当继续并进行到查询一的X锁记录的零点,U 与 X 冲突,无法继续,这时候查询二等待查询一释放锁

  • 查询一的第二个更新開始运行。依次扫描每条记录。同一个事务内不会有冲突。所以它不会与自己之前锁定的记录有冲突,但进行到查询二锁定的记录的时候,它也无法获得 U 锁。它须要等待查询二释放资源。

    这个时候就形成了相互等待,符合死锁条件

  • 假设查询二须要更新的记录在查询一的第一个更新记录之后。则不会有死锁。由于查询二在扫描到查询一第一个更新的记录时就会由于锁冲突等待了,这个时候它没有对不论什么记录设置与查询一的操作有冲突的锁。我自己測试的时候没有死锁,就是这种情况。

    注意这里面提到的顺序。是数据读取的顺序,不一定与存储顺序一样,磁盘上记录的顺序也不一定与INSERT的记录顺序一样,这也是我用相同条件没有測试出死锁的原因(我的环境中,恰好读出的顺序与INSERT的顺序不一样)

更新时,记录读取的顺序,能够通过Profile跟踪的Lock:Acquired (加锁)事件来看。涉及大量数据时,假设server支持。还会有并发读取。这也是分析死锁时要考虑的因素



初步了解更新锁(U)与排它锁(X)的更多相关文章

  1. Sql Server 锁 排它锁 更新锁 共享锁

    引用别人的.有时间整体整理下. 引用地址:http://www.cnblogs.com/wenjl520/archive/2012/08/24/2654412.html 锁的概述 一. 为什么要引入锁 ...

  2. 在SQL Server里为什么我们需要更新锁

    今天我想讲解一个特别的问题,在我每次讲解SQL Server里的锁和阻塞(Locking & Blocking)都会碰到的问题:在SQL Server里,为什么我们需要更新锁?在我们讲解具体需 ...

  3. 使用Sqlserver更新锁防止数据脏读

    有时候我们需要控制某条记录在程序读取后就不再进行更新,直到事务执行完释放后才可以.这时候我们就可以将所有要操作当前记录的查询加上更新锁,以防止查询后被其它事务修改.这种操作只锁定表中某行而不会锁定整个 ...

  4. SqlServer中的更新锁(UPDLOCK)

    UPDLOCK.UPDLOCK 的优点是允许您读取数据(不阻塞其它事务)并在以后更新数据,同时确保自从上次读取数据后数据没有被更改.当我们用UPDLOCK来读取记录时可以对取到的记录加上更新锁,从而加 ...

  5. 数据库更新锁WITH UPDLOCK

    今天因为并发的问题,又讨论了一遍.之前以为同时两个线程开启,线程A加了更新锁,线程B没有加,线程A更新后,线程B也会继续下去代码.但是今天测试了一下,原来线程A更新后(解锁),线程B将不会继续,会出现 ...

  6. 【SqlServer】SqlServer中的更新锁(UPDLOCK)

    UPDLOCK.UPDLOCK 的优点是允许您读取数据(不阻塞其它事务)并在以后更新数据,同时确保自从上次读取数据后数据没有被更改.当我们用UPDLOCK来读取记录时可以对取到的记录加上更新锁,从而加 ...

  7. 【转】Mysql学习---MySQL悲观锁中的排它锁

    [原文]https://www.toutiao.com/i6595305814087434760/ 悲观锁中的排它锁. 排它锁关键字:for update 特点:会锁住行或者表,防止其他事务进行修改操 ...

  8. SqlServer中的更新锁(UPDLOCK和READPAST)

    UPDLOCK和READPAST,通过UPDLOCK和READPAST的结合我们能够解决许多问题,比如我当前项目中对于更新预约人数,则用到了UPDLOCK和READPAST,因为考虑到并发如果固定预约 ...

  9. 分布式锁 ----zookeeper实践 (排它锁)

    排它锁概念: Exclusive Locks,被称为X锁,写锁,独占锁.如果事物T1对数据对象O1加上了排它锁,那么在整个加锁期间,只允许事务T1对O1进行读写操作,其他事务必须等到T1释放锁后才能进 ...

随机推荐

  1. tips 前端 背景与元素的透明和模糊

    碰到好几次这样的情况了: 一个带点儿文艺效果 背景图片模糊 而一行别致的文字清晰的悬浮在背景上(口胡,加点美好的想象,生活会更美好) 第一反应是 this is easy. cause i know ...

  2. RelativeSource

    当一个Binding有明确的数据来源时可以通过为Source或ElementName赋值的办法让Binding与之关联,有的时候由于不能确定Source的对象叫什么名字,但知道它与作为Binding目 ...

  3. Access数据库 INSERT INTO 失败

    一次操作Access数据库,插入一条数据,总是失败,如下: 通过赋值,一个字段一个字段的排查,最终确定是UserAge字段处有问题. 最初,UserAge字段是 %d 类型的,赋值20,可成功插入数据 ...

  4. 【linux高级程序设计】(第九章)进程间通信-管道 2

    文件描述符重定向 cat<test01  :将输入重定向到test01文件 cat>test02<test01  :将标准正确输出重定向到test02文件,输入设备重定向到test0 ...

  5. hdu 2489(状态压缩+最小生成树)

    Minimal Ratio Tree Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Other ...

  6. @Html.Raw()用法

    @Html.Raw() 方法输出带有html标签的字符串, 如:@Html.Raw("<div style='color:red'>输出字符串</div>" ...

  7. J.U.C并发框架源码阅读(七)CyclicBarrier

    基于版本jdk1.7.0_80 java.util.concurrent.CyclicBarrier 代码如下 /* * ORACLE PROPRIETARY/CONFIDENTIAL. Use is ...

  8. Longest Common Substring($LCS$)

    Longest Common Substring(\(LCS\)) 什么是子序列? 子序列就是某一个序列的不连续的一部分. 如图, \(abcde\)就是图中序列的一个子序列. 公共子序列 公共子序列 ...

  9. HDOJ 3516 Tree Construction 四边形优化dp

    原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=3516 题意: 大概就是给你个下凸包的左侧,然后让你用平行于坐标轴的线段构造一棵树,并且这棵树的总曼哈顿 ...

  10. luogu P1182 数列分段Section II

    题目描述 对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小. 关于最大值最小: 例如一数列4 2 4 5 1要分成3段 将其如下分段: [4 ...