两个事务 update同一张表出现的死锁问题 (转载)
引言
近来做省一级计算机一级考试系统的时候,学生端进行大批量判分的时候,出现了这样的问题(事务(进程 ID 262)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。):
这个就是我们在代码中写了大批量的update语句,用trace Profiler ,我们对死锁追踪是这样的:
分析:
我们来分析一下上面的图,上面为DeakLock graph,图中左右两边的椭圆形相当于一个处理节点(Process Node),当鼠标移动到上面的时候,可以看到内部执行的代码,如update,Insert,Delete等等,有打叉的左边的椭圆形就是牺牲者,没有打叉的为优胜者。中间两个长方形就是一个资源节点(Resource Node),描述数据库中的对象,如一个表、一行或者一个索引。在我们当前的实例中,描述的是:假设左边的椭圆形为Process Node1,右边的椭圆形为Process Node2,上面的长方形为Resource Node1,下面为Resource Node2,Process Node1对Resource Node1申请一个U锁,但是,Resource Node1被Process Node2的X锁占有;另一边,Process Node2对Resource Node2申请一个U锁,但是Resource Node2被Process Node1的X锁占有。这样就形成了一个资源占有的死循环,这个时候,sql server会在sq_lock中检测到死锁,这个时候,就会出现一个牺牲品的事情,以至于系统能够继续运行。
我们可以看一下,两个事务分为什么:
右边的Process Node2为:
可以粗略的看到,是两个update语句出现了死锁的问题。
那么为什么两条update语句会出现死锁的问题呢?我们通过一个简单的数据库进行模拟一下当时两条update语句的死锁。
模拟:
下面是我们的建表语句:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[table1](
[A] [nvarchar](10) NULL,
[B] [nvarchar](10) NOT NULL,
[C] [nvarchar](10) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa1', N'b1', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa2', N'b3', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b4', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b5', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b2', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b6', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b7', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b8', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa1', N'b9', N'') INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa1', N'b1', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa2', N'b3', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b4', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b5', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b2', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b6', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b7', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b8', N'')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa1', N'b9', N'')
我们第一个update事务为:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
begin tran
print convert(nvarchar(30),convert(datetime,getdate(),121),121)
update t_table
set A='aa1'
where B='b3'
print convert(nvarchar(30),convert(datetime,getdate(),121),121)
EXEC sp_lock @@spid waitfor delay '00:00:10' update t_table
set A='aa2'
where B='b8'
EXEC sp_lock @@spid
print convert(nvarchar(30),convert(datetime,getdate(),121),121)
commit tran
第二个update事务为:
SET TRANSACTION ISOLATION LEVEL Read UNCOMMITTED
begin tran
update table1
set A='aa3'
where B='b1' EXEC sp_lock @@spid
commit tran
两个事物首先我们触发事务一,然后紧接着,我们触发事务二,这个时候,消息中出现:
这个时候,我们追踪死锁的时候,是这样的:
两个Process Node节点执行的事务为:
可以看到,其实是我们上面写的事务,一个是牺牲品,另外一个为优胜品。那么我们现在最大的疑问,就是,他们的申请U锁和X锁,是如何形成死锁的呢?
我们首先看一下现在table1表中的数据:
、
我们把数据放到Excel中进行分析:
首先我运行事务一(Process Node1),我们看看它的代码:首先要进行这一步:update table1 set A='aa1' where B='b3' ,系统运行这一步的时候,是从第一条数据开始加上U锁的,当检查到第二条数据的时候,U锁发现,符合B='b3'时,将U锁升级为X锁,这个时候,我们就在第一条蓝线这里表示为X,接下来,继续对第3条记录进行U锁,然后为第4条,第5条,到了第11条的时候,又存在了符合B='b3'时,将U锁升级为X锁,也就是我表示的第二条蓝线,继续加U锁,发现,整张表都已经所扫描完了,没有存在符合条件的了,这个时候,如果Process Node1到这里就运行完了,我们应该释放X锁,但是Process Node1,后面还有代码:waitfor delay '00:00:10' ,也就是等待了10秒钟,这个时候,只要事务不执行完成,X锁不会释放。
而此时,我已经运行了事务二(Process Node2),我们看到它的代码是: update table1 set A='aa3' where B='b1',通过上面说明,我们同样分析一下该update语句的执行过程,Process Node2将table1中的数据从第一条开始加U锁,这个时候,第一条数据就符合 B='b1',这个时候,U锁升级为X锁,继续往下执行的时候,发现第二条数据已经被Process Node1的X锁占有,X锁为排它锁的原因为不与其他锁兼容,也就是说,不能加U锁,这个时候,Process Node2只能等待Process Node1将X锁释放,而Process Node1 没有执行完成,是不会释放X锁的,所以Process Node2 等待Process Node1释放第二条记录的X锁。
这个时候,我们发现Process Node1中waitfor delay '00:00:10' ,代码已经运行完成,之后,进行update table1 set A='aa2' where B='b8'代码,这条语句从第一条数据开始加U锁,但是,我们发现第一条数据已经被Process Node2的X锁占用,也就是说,Process Node1需要等待Process Node2 的X锁释放才行,所以Process Node1 在第一条记录这里等待。
我们最后看到的应该是这样的:
因此出现了上面的现象,就是Process Node2 想要对Resource Node1(第二条记录)请求U锁,但是,Resource Node1 被Process Node1 的X锁占用,而Process Node2 想要对Resource Node2(第一条记录)请求U锁,但是Resource Node2被Process Node2 的X锁占用。
解决方案:
我们的解决方案是这样的,在B的字段加上非聚集索引,就可以了,为什么呢?应为聚集索引和非聚集索引加U锁的时候,都不是整张表进行扫描的,而是直接就可以根据索引找到这条记录进行升级锁,所以,不会出现上面的死锁的问题。
结束语:
对于数据库锁的问题,我们要懂得锁的运行原理。不要仅仅停留在表面上,有时候,我们需要动手模拟sql server的运行原理来解决我们数据库中的死锁问题,原理很重要。
两个事务 update同一张表出现的死锁问题 (转载)的更多相关文章
- SQL两个事务update同一张表出现的死锁问题(waitfor delay)
抄录网址:https://blog.csdn.net/qiumuxia0921/article/details/50574879 下面是我们的建表语句: SET ANSI_NULLS ON GO SE ...
- sqlserver默认隔离级别下并发批量update同一张表引起的死锁
提到死锁,最最常规的场景之一是Session1 以排它锁的方式锁定A表,请求B表,session2以排它锁的方式锁定B表,请求A表之类的,访问顺序不一致导致死锁的情况本文通过简化,测试这样一种稍显特殊 ...
- 级联两个bootstrap-table。一张表显示相关的数据,通过点击这张表的某一行,传过去对应的ID,刷新另外一张表。
二张表的代码(我用的插件,大家可以去网上直接下载http://issues.wenzhixin.net.cn/bootstrap-table/): <div class="contai ...
- EF——一个实体对应两张表,两个实体对应一张表 06 (转)
本篇日记我们将详细探讨如何将表现领域的类映射到现有的数据库.现在的经济形势不是太好,很多公司都取消了开发新系统的预算.在这种情况下,通常的做法是把原有的几个系统修改一下做个集成,先凑合用着得了.如果要 ...
- 42. oracle通过两张表的一个字段对应,update其中一张表的某个字段
update A a set a.A2 = (select b.B2 from B b where b.B1=a.A1) where exists (select 1 from B where B.B ...
- Sql Server两个数据库中有一张表的结构一样,怎么快速将一张表中的数据复制到另一个表中
1,下面这句会把表2数据删除,然后把表1复制到表一,两表内容一样 SELECT * into 表2 FROM 表1 2,这句只追加,不删除表2的数据 insert into 表1 select * f ...
- 2016.2.13 (年初六) oracle两张表update方法
A表customers和B表tmp_cust_city有3个相同字段, customer_id,city_name,customer_type 现要根据b表更新a表 更新一个字段情况: update ...
- Oracle两张表关联批量更新其中一张表的数据
Oracle两张表关联批量更新其中一张表的数据 方法一(推荐): UPDATE 表2 SET 表2.C = (SELECT B FROM 表1 WHERE 表1.A = 表2.A) WHERE EXI ...
- 关于跨DB增量(增、改)同步两张表的数据小技巧
有些场景下,需要隔离不同的DB,彼此DB之间不能互相访问,但实际的业务场景又需要从A DB访问B DB的情形,这时怎么办?我认为有如下常规的三种方案: 1.双方提供RESET API,需要访问不同DB ...
随机推荐
- csharp:A Custom CheckedListBox with Datasource
/// <summary> /// (eraghi) /// Custom CheckedListBox with binding facilities (Value property) ...
- centos使用ngnix代理https
自己建web服务器,考虑到安全问题需要用到https. 在此使用nginx的反向代理功能实现https 腾讯云证书安装指引 ssl.conf 配置 // http请求重定向https server { ...
- Ubuntu增加一个用户并给普通用户赋予root权限的方法
1.添加用户,首先用adduser命令添加一个普通用户,命令如下: #adduser tommy //添加一个名为tommy的用户#passwd tommy //修改密码Changing pass ...
- 中专生自学Android到找到工作的前前后后
我是一名中专生,在学校里读的是计算机专业,但是由于学校不好大部分同学都不爱学习来这里几乎大部分都是在混日子的,虽然我中考的成绩不差,但是因为家里穷考虑到以后没钱读大学我毅然来到这里,虽然是中专,但是我 ...
- java中方法调用在内存中的体现
在java中,方法以及局部变量(即在方法中声明的变量)是放在栈内存上的.当你调用一个方法时,该方法会放在调用栈的栈顶.栈顶的方法是目前正在执行的方法,直到执行完毕才会从栈顶释放.我们知道,栈是一种执行 ...
- 表空间(TableSpace)
我们知道Oracle数据库真正存放数据的是数据文件(Data File),oracle表空间(tablespace)实际上是一个逻辑的概念,在物理上是并不存在的,那么把一组data files捻在一起 ...
- F5 IIS Log获取客户端源IP
1.配置F5启用X-Forwarded-For方法: 1:Local Traffic-Profiles-Http-改"Insert XForwarded For"为Enable 2 ...
- 什么是TTL值?(简单明了的解释)
什么是TTL值? TTL值全称是“生存时间(Time To Live)”,简单的说它表示DNS记录在DNS服务器上的缓存时间. 要理解TTL值,请先看下面的一个例子:假设,有这样一个域名myhost. ...
- Java学习---MD5加密算法
前言 在我们日常的程序开发中,或多或少会遇到一些加密/解密的场景,比如在一些接口调用的过程中,我们(Client)不仅仅需要传递给接口服务(Server)必要的业务参数,还得提供Signature(数 ...
- Spring注解开发-全面解析常用注解使用方法之生命周期
本文github位置:https://github.com/WillVi/Spring-Annotation/ 往期文章:Spring注解开发-全面解析常用注解使用方法之组件注册 bean生命周期 ...