zoukankan      html  css  js  c++  java
  • 更新一张没有主键的数据表,引发的死锁

    不介绍背景,直接上例子

    首先我们创建这样的一张表,没有主键,添加下面的数据

    然后我们分别创建下面的连个连接查询

    查询1:

    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
    --SERIALIZABLE
    --READ UNCOMMITTED
    begin tran
    print convert(nvarchar(30),convert(datetime,getdate(),121),121)

    update table1
    set A='aa'
    where B='b2'

    -- print convert(nvarchar(30),convert(datetime,getdate(),121),121)
    waitfor delay '00:00:10'
    update table1
    set A='aa'
    where B='b4'
    --EXEC sp_lock2 @@spid
    EXEC sp_lock @@spid
    print convert(nvarchar(30),convert(datetime,getdate(),121),121)
    commit tran

    查询2:

    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
    --DBCC USEROPTIONS
    --begin try
    begin tran
    print convert(nvarchar(30),convert(datetime,getdate(),121),121)
    update table1
    set A='aa'
    where B='b8'

    --EXEC sp_lock2 @@spid
    waitfor delay '00:00:5'
    EXEC sp_lock @@spid
    print convert(nvarchar(30),convert(datetime,getdate(),121),121)
    commit tran

    首先执行查询一,然后马上切换到查询二

    再马上暂停查询二,执行 

    EXEC sp_lock @@spid

    发现结果是:

    最后一条记录对应的就是table1,这时我们会看到在table1上已经加上了表的意向锁。

    如果不做停止操作,执行的结果不会有异常。

    然后我们再调整一下查询2:

    update table1 
    set A='aa'
    where B='b1'

    重复上面的步骤会发现执行的锁信息如下:

    这时我们会发现执行的结果出现异常

    消息 1205,级别 13,状态 45,第 6 行
    事务(进程 ID 53)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。

    其中有一个规律,我们查询一中有B='b2',如果查询二中的条件值在b2之前就会出现死锁,而在b2之后就会正常执行事务。

    我们来看一下查询一,在执行过程中的锁信息:

    当执行查询一的过程中,再执行查询二,对于第一种情况,将整个表加上了意向锁,等待查询一结束释放资源后再

    执行查询二,而对于第二种情况则出现了资源的争夺,导致死锁。

    在这个过程中我们看到了IX,IX是意向锁,什么是意向锁呢?
    意向锁的含义是如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对任一结点加锁时,必须先对它的上层结点加意向锁。

    当我们给行记录加上X锁的时候,就会在page和TAB上加意向锁。

    例如,对任一元组加锁时,必须先对它所在的关系加意向锁。

    于是,事务T要对关系R1加X锁时,系统只要检查根结点数据库和关系R1是否已加了不相容的锁,而不再需要搜索和检查R1中的每一个元组是否加了X锁。

     到了现在还没有搞清原因所在,最后在一位MVP的帮助下,结合Profiler跟踪,终于看明白了原因

     通过Trace 发现了原因, 通过 Profile 跟踪锁的加锁(Lock:Acquired) 和释放锁(Lock:Released ) 这两个事件可以发现, 更新录的时候,会对扫描的每条记录都会有更新锁 (U) 的加锁和释放锁的操作
     
    了解了这个过程, 那么对于死锁就很好解释:
      对于两个查询而言, 查询一的第一个更新扫描所有记录,扫描过程会对扫描的每一条记录下U锁, 如果满足更新条件,则转化为X锁更新;如果不满足更新条件,则释放U锁。更新一完成后,第一个更新的记录保持X锁(因为事务没有完成),查询一等待第二个更新操作
     
     对于查询二的更新,与查询一的更新过程相同,如果更新的记录在查询一第一个更新的记录前,那么查询二所更新的记录也会持有X锁,但在扫描记录进行到查询一条一个更新的记录的时候,需要等待查询一完成(已经有X锁的记录无法下U锁),这个时候查询二被查询一Block
     对于查询一, 第二个更新进行时,它也扫描所有记录,进行到更新二所在的记录的时候,它无法取得U锁(因为已经被查询二下了X锁), 这个时候查询一等待查询二完成。 在这种情况下,查询一和查询二就是互相等待了,符合死锁条件
     
    如果查询二更新的记录在查询一第一个更新的记录之后,那么查询二的U扫描行到查询一第一次更新记录的时候,就会因为锁冲突导致无法进行下去,必须等待查询一完成, 这个时候查询二没有会导致查询二第一个更新无法进行的锁, 也就不会导致死锁了

  • 相关阅读:
    悲观锁乐观锁实战
    悲观锁
    乐观锁
    mysql数据库怎么设置乐观锁
    猴子吃桃问题
    算法题
    面试总结
    分布式系统理论(二):一致性协议Paxos
    职工工资管理
    79-WordSearch
  • 原文地址:https://www.cnblogs.com/wanglg/p/3751895.html
Copyright © 2011-2022 走看看