首页 > 数据库 >动手分析SQL Server中死锁形成原因

动手分析SQL Server中死锁形成原因

时间:2023-09-28 21:23:10浏览次数:48  
标签:事务 BillInfo GRANT Server 死锁 65 SQL billdetail

摘要

上次在《动手分析SQL Server中的事务中使用的锁》一文中分析了事务中是如何使用锁,对于insert、update和select中使用的锁以实例的方式进行了初步分析,不过日常使用的时候都是很多事务同时执行,有时候难免会遇到死锁和阻塞的问题,近期在生产环境就遇到了一些死锁方面的问题,本文计划从锁的角度复现死锁过程,以期对于死锁形成机理有深入理解。

本文属于对于基础知识的实践应用,需要将锁的共享、互斥等应用于实际的场景分析。本文计划模拟生产环境中死锁场景,以锁的角度分析死锁的实际发生过程及相应的解决方案。

死锁的形成条件是比较明确的,需要满足四个方面的要求:

  1. 不可剥夺:锁只能由持有者释放,不能被其他进程强行夺取;
  2. 互斥性:锁定的资源是独占的,任意时间只有一个持有者;
  3. 循环等待:不同持有者之间相互依赖对方持有的锁;
  4. 请求和保持:申请新的资源的时候仍然占有之前的资源;

场景再现

准备工作

首先生成模拟数据表:

USE [Test]
GO
/****** Object:  Table [dbo].[billdetail]    Script Date: 2023/6/29 21:13:30 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[billdetail](
	[id] [varchar](36) NOT NULL,
	[billid] [varchar](36) NULL,--用于与BillInfo表的id进行关联
	[createtime] [datetime] NULL,
	[lastmodifytime] [datetime] NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING ON
GO
/****** Object:  Index [pk_billdetail]    Script Date: 2023/6/29 21:13:30 ******/
CREATE CLUSTERED INDEX [pk_billdetail] ON [dbo].[billdetail]
(
	[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO
/****** Object:  Table [dbo].[BillInfo]    Script Date: 2023/6/29 21:13:30 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[BillInfo](
	[id] [varchar](36) NOT NULL,
	[code] [varchar](256) NULL,
	[begintime] [datetime] NULL,
	[endtime] [datetime] NULL,
	[createtime] [datetime] NULL,
	[lastmodifytime] [datetime] NULL,
PRIMARY KEY CLUSTERED 
(
	[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING ON
GO
/****** Object:  Index [idx_billdetail_billid]    Script Date: 2023/6/29 21:13:30 ******/
CREATE NONCLUSTERED INDEX [idx_billdetail_billid] ON [dbo].[billdetail]
(
	[billid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO

插入模拟数据:

insert into BillInfo values('BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8','123654789',GETDATE(),GETDATE(),GETDATE(),GETDATE())
insert into billdetail values (newid(),'BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8',GETDATE(),GETDATE())

事务详情

生产环境遇到的死锁问题有两种类型,一种是两个事务之间直接死锁,另一种是这两个事务通过第三个事务形成了死锁。下面用模式的表结构和数据来重现这两种死锁场景。

事务一的流程:

  1. 通过主键更新BillInfo字段;
  2. 根据BillId字段删除BillDetail数据;
  3. 插入BillDetail的新数据;

以上文的模拟表为基础,事务一的SQL为:

begin tran

update BillInfo set lastmodifytime=GETDATE() where id ='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

delete from billdetail where billid='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

insert into billdetail values (newid(),'BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8',GETDATE(),GETDATE())

--rollback tran

事务二的流程:

  1. 根据BillId字段更新BillDetail数据;
  2. 根据主键更新BillInfo字段;
begin tran

update billdetail set lastmodifytime=getdate() where  billid='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

update BillInfo set lastmodifytime=GETDATE() where id ='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

--rollback tran

事务三的流程:

  1. 根据主键更新BillInfo字段;
begin tran

update BillInfo set endtime=GETDATE() where id ='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8' and code not in ('1','2')

--rollback tran

事务一执行过程中使用的锁统计表(会话编号65代表事务一,下文会使用相同会话编号):

会话编号 被锁定的资源类型 请求的状态 请求类型 资源描述 对象名称
65 DATABASE GRANT S Test
65 PAGE GRANT IX 1:280 BillInfo
65 PAGE GRANT IX 1:464 billdetail
65 PAGE GRANT IX 1:472 billdetail
65 KEY GRANT X (2b7e3a26dcce) billdetail
65 KEY GRANT X (94b3c27cd2da) billdetail
65 KEY GRANT X (abd4d799dd7d) BillInfo
65 KEY GRANT X (d2bdbf05a1de) billdetail
65 KEY GRANT X (6d70475fafca) billdetail
65 OBJECT GRANT IX BillInfo
65 OBJECT GRANT IX billdetail

简单汇总下事务一使用的锁:

  1. 数据库Test上加的共享锁(资源类型为Database)
  2. BillInfo、BillDetail这个表上加的意向排他锁(资源类型为object)
  3. BillInfo、BillDetail表数据页上的意向排他锁(资源类型为page)
  4. BillInfo、BillDetail索引上的排他锁(资源类型为key)

事务二和事务三语句申请的资源及其锁类型也是类似的。上表中出现的锁使用模式有三种:共享(S)、意向排他(IX)和排他(X),其中不同事务之间S和IX都是可以有条件地共享数据,唯有X模式为不可共享的类型,所有在相同数据上使用排他锁的事务都必须串行执行,而由于事务一中使用了多个数据的排他锁,因此如果有其他事务使用锁的顺序与事务一不一致,就很容易出现死锁的问题。

两事务死锁过程

两事务死锁的复现步骤(用两个SSMS查询窗口分别按照如下顺序执行):

步骤一:事务一开启事务并执行更新BillInfo的语句

begin tran

update BillInfo set lastmodifytime=GETDATE() where id ='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

步骤二:事务二开启事务并执行BillDetail的更新语句

begin tran

update billdetail set lastmodifytime=getdate() where  billid='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

步骤三:事务一继续执行删除BillDetail的语句

delete from billdetail where billid='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

步骤四:事务二继续执行更新BillInfo的语句

update BillInfo set lastmodifytime=GETDATE() where id ='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

此时查看数据库中存在的锁(会话65为事务一,会话55为事务二)

会话编号 被锁定的资源类型 请求的状态 请求类型 资源描述 对象名称
55 DATABASE GRANT S Test
55 PAGE GRANT IX 1:280 BillInfo
55 PAGE GRANT IX 1:464 billdetail
55 KEY WAIT X (abd4d799dd7d) BillInfo
55 KEY GRANT X (d2bdbf05a1de) billdetail
55 KEY GRANT U (6d70475fafca) billdetail
55 PAGE GRANT IU 1:472 billdetail
55 OBJECT GRANT IX BillInfo
55 OBJECT GRANT IX billdetail
65 OBJECT GRANT IX billdetail
65 KEY GRANT X (abd4d799dd7d) BillInfo
65 KEY WAIT U (6d70475fafca) billdetail
65 OBJECT GRANT IX BillInfo
65 PAGE GRANT IU 1:472 billdetail
65 PAGE GRANT IX 1:280 BillInfo
65 DATABASE GRANT S Test
66 DATABASE GRANT S Test

通过这个表可以很清楚的看出来事务一在等待资源6d70475fafca上增加U锁,该资源被事务二持有;而事务二在等待资源abd4d799dd7d上增加U锁,而该资源被事务一持有。

过了几秒钟后数据库自动检测发现存在死锁进行了处理,事务一被强制中止,事务二得以继续执行。

三事务死锁过程

三事务死锁的复现步骤(用三个SSMS查询窗口分别按照如下顺序执行):

步骤一:事务一开启事务并执行更新BillInfo的语句

begin tran

update BillInfo set lastmodifytime=GETDATE() where id ='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

步骤二:事务三执行BillInfo的更新语句


update BillInfo set endtime=GETDATE() where id ='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8' and code not in ('1','2')

步骤三:事务二开启事务并执行BillDetail的更新语句

begin tran

update billdetail set lastmodifytime=getdate() where  billid='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

步骤四:事务一继续执行删除BillDetail的语句

delete from billdetail where billid='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

步骤五:事务二继续执行更新BillInfo的语句

update BillInfo set lastmodifytime=GETDATE() where id ='BBAE2B81-7C0A-44A5-8DF5-93DF9B3E40A8'

此时查看数据库中存在的锁(会话65为事务一,会话55为事务二,会话70为事务三)

会话编号 被锁定的资源类型 请求的状态 请求类型 资源描述 对象名称
55 DATABASE GRANT S Test
55 PAGE GRANT IX 1:280 BillInfo
55 PAGE GRANT IX 1:464 billdetail
55 PAGE GRANT IU 1:472 billdetail
55 KEY WAIT X (abd4d799dd7d) BillInfo
55 KEY GRANT X (d2bdbf05a1de) billdetail
55 KEY GRANT U (6d70475fafca) billdetail
55 OBJECT GRANT IX BillInfo
55 OBJECT GRANT IX billdetail
65 OBJECT GRANT IX billdetail
65 OBJECT GRANT IX BillInfo
65 KEY WAIT U (6d70475fafca) billdetail
65 PAGE GRANT IU 1:472 billdetail
65 KEY GRANT X (abd4d799dd7d) BillInfo
65 PAGE GRANT IX 1:280 BillInfo
65 DATABASE GRANT S Test
70 DATABASE GRANT S Test
70 PAGE GRANT IU 1:280 BillInfo
70 KEY WAIT U (abd4d799dd7d) BillInfo
70 OBJECT GRANT IX BillInfo

通过这个表可以看出事务一等待资源6d70475fafca,该资源被事务二占用,而事务二在等待资源abd4d799dd7d,该资源被事务一持有,事务三等待的资源是abd4d799dd7d,同样也是被事务一持有。

由于执行时序的问题,虽然事务二和事务三在等待相同的资源,但是事务二时序排在事务三的后面,因此三个事务形成了循环等待的死锁状态。

三事务死锁示意图

死锁的形成过程分析起来很复杂,但是解决起来并不复杂,只要让事务一和事务二修改数据表的顺序保持统一,对于相同数据资源的使用就只会排队进行而不会形成死锁。

总结

数据库中死锁的形成原因有四个必要条件,由于数据库需要满足事务性的要求,不可避免地会使用各种锁控制资源地并发使用,如果事务中对于不同资源地使用顺序不一致,那么不同地事务之间就很容易因为对于资源的循环依赖而形成死锁,解决死锁地最好办法就是SQL语句使用的资源无论在什么事务中都保持相同的使用顺序。

参考文献

标签:事务,BillInfo,GRANT,Server,死锁,65,SQL,billdetail
From: https://www.cnblogs.com/zhu-wj/p/17577041.html

相关文章

  • Java内存分析工具实践之Spark客户端线程死锁
    ......
  • PostgreSQL数据库事务系统——获取virtual transaction id
    如果一个事务没有进行INSERT、UPDATE、DELETE操作,那么就步会分配事务ID,但事务仍然用一个虚拟事务ID代表自己。虚拟事务ID由两部分组成,第一部分是BackendID,另一个是每个会话自己维护的本地事务ID计数器。通过两部分组合,能保证这个虚拟事务ID的唯一性。在PostgreSQL数据库IPC——SI......
  • SockerServer
    publicclassSocketServerextendsThread{privateintport;privateSocketMsgListenersocketMsgListener;publicstaticList<RfidText>rfidTextList=null;publicSocketServer(intport,SocketMsgListenersocketMsgListener){this.p......
  • sqlserver多选字段查询另外一个字段拼接
    selectexttextfield6fromassetswherecharindex(id,'8a070cd88ab2a995018ad0c69def060a,8a070cd88ab2a995018ad0c69ffb0625')>0实例:selectstuff((select','+exttextfield6fromassetswherecharindex(id,'8a070cd88ab2a995018ad0c69def......
  • MySQL 45讲笔记(2)
    全局锁和表锁根据加锁的范围,MySQL里面的锁大致可以分成全局锁、表级锁和行锁三类全局锁顾名思义,全局锁就是对整个数据库实例加锁。MySQL提供了一个加全局读锁的方法,命令是Flushtableswithreadlock(FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线......
  • MySql 数据库 对表中数据的操作
    对表中数据的操作--向表中插入数据insert[ignore]into表名(字段列表)values(值列表)usescoredb;showtables;descdepartments;insertintodepartments(id,deptno,deptname,director,location)values(1,101,"张xx","张院长","新乡"......
  • postgresql xid回卷预防及排查
    监控WITHmax_ageAS(SELECT2000000000asmax_old_xid,settingASautovacuum_freeze_max_ageFROMpg_catalog.pg_settingsWHEREname='autovacuum_freeze_max_age'),per_database_statsAS(SELECTdatname......
  • MySql 数据 管理表的操作
    管理表的操作usescoredb;--查看数据库中有哪些表showtables;showtablesfrombipowernode;--查看数据表的基础结构showcolumnsfromdepartment;descdepartment;--查看表的详细结构,查看表的建表语句showcreatetabledepartment;--1.修改表名altertab......
  • VMware 安装windows server 2008 R2 虚拟机 及VMtools安装
    VMware安装windowsserver2008R2虚拟机并安装VMtools1.新建虚拟机按照图示步骤操作新建虚拟机2.选择安装系统这里我选择安装企业版选择自定义等待安装首次登陆需要修改密码3.给系统安装VMtools默认下一步即可若安装VMtools按钮为灰色或遇到......
  • TypeOrm -如何运行原始sql查询?
    在我使用v0.2.25打字机之前我以前在那里运行这样的原始查询:constusers=awaitgetManager().query("SELECT*FROMusers");复制但从现在起,我将typeOrm更新为最新版本:v0.3.10如何在最新版本中实现相同的原始查询。因为在最新版本中,不推荐使用getManager()......