更新一张没有主键的数据表,引发的死锁

不介绍背景,直接上例子

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

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

查询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扫描行到查询一第一次更新记录的时候,就会因为锁冲突导致无法进行下去,必须等待查询一完成, 这个时候查询二没有会导致查询二第一个更新无法进行的锁, 也就不会导致死锁了

原文地址:https://www.cnblogs.com/wanglg/p/3751895.html