首页 > 数据库 >SQLSERVER 语句交错引发的死锁研究

SQLSERVER 语句交错引发的死锁研究

时间:2023-03-31 20:32:51浏览次数:43  
标签:语句 00 SET dbo Employees SQLSERVER 死锁 WHERE

一:背景

1. 讲故事

相信大家在使用 SQLSERVER 的过程中经常会遇到 阻塞死锁,尤其是 死锁,比如下面的输出:

(1 row affected)
Msg 1205, Level 13, State 51, Line 5
Transaction (Process ID 62) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

要解决死锁问题,个人感觉需要非常熟知各种隔离级别,尤其是 可提交读 模式下的 CURD 加解锁过程,这一篇我们就来好好聊一聊。

二:死锁简析

1. 一个测试案例

开启两个会话 6566 ,分别使用如下查询。

-- 会话 65 --
BEGIN TRAN
UPDATE dbo.Employees SET Title='Dr.' WHERE EmployeeID=1;
WAITFOR DELAY '00:00:10'
SELECT * FROM dbo.Orders WHERE OrderID=10258
ROLLBACK

-- 会话 66 --
BEGIN TRAN
UPDATE  dbo.Orders SET  ShipAddress='上海' WHERE OrderID=10258
WAITFOR DELAY '00:00:10'
SELECT * FROM dbo.Employees WHERE EmployeeID=1;
ROLLBACK

两个会话非常简单,交错的对 EmployeesOrders 进行 SELECT 和 UPDATE 操作,稍等几秒后就会出现死锁。

SQLSERVER 语句交错引发的死锁研究_sql

2. 寻找死锁源头

当我们的应用程序拿到了这样的输出其实作用是不大的,要想溯源最好就是通过不断的对 SQLSERVER 进行监视来捕获死锁时的上下文信息,手段也有很多:

  • SQL Server Profile
  • DBCC TRACEON(1222)
  • DMV VIEW

这里我们就用第一种方式,一定要勾选 TextData 项,因为这里面会有死锁上下文信息的xml表示,截图如下:

SQLSERVER 语句交错引发的死锁研究_sql_02

将 profile 开启后,重新执行刚才的两个查询,一旦出现死锁,profile 就会成功捕获,然后 copy 出 TextData 项,截图如下:

SQLSERVER 语句交错引发的死锁研究_死锁_03

<deadlock-list>
 <deadlock victim="process2d69c9748c8">
  <process-list>
   <process id="process2d69c9748c8" taskpriority="0" logused="324" waitresource="KEY: 7:72057594043170816 (8194443284a0)" waittime="1304" ownerId="70740" transactionname="user_transaction" lasttranstarted="2023-02-19T22:11:26.413" XDES="0x2d6a0200428" lockMode="S" schedulerid="5" kpid="13816" status="suspended" spid="66" sbid="0" ecid="0" priority="0" trancount="1" lastbatchstarted="2023-02-19T22:11:26.413" lastbatchcompleted="2023-02-19T22:11:26.410" lastattention="1900-01-01T00:00:00.410" clientapp="Microsoft SQL Server Management Studio - Query" hostname="DESKTOP-STS8TPB" hostpid="1696" loginname="DESKTOP-STS8TPB\Administrator" isolationlevel="read committed (2)" xactid="70740" currentdb="7" currentdbname="Northwind" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
    <executionStack>
     <frame procname="adhoc" line="5" stmtstart="24" stmtend="128" sqlhandle="0x020000007383d935b349bc173c0f104de14945e9a526322b0000000000000000000000000000000000000000">
unknown     </frame>
     <frame procname="adhoc" line="5" stmtstart="204" stmtend="294" sqlhandle="0x020000002c3b203105961d63d10b17e54ed6ac081105f9450000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>

BEGIN TRAN
UPDATE  dbo.Orders SET  ShipAddress='上海' WHERE OrderID=10258
WAITFOR DELAY '00:00:10'
SELECT * FROM dbo.Employees WHERE EmployeeID=1;
ROLLBACK
    </inputbuf>
   </process>
   <process id="process2d6ae694ca8" taskpriority="0" logused="368" waitresource="KEY: 7:72057594044088320 (59ce0997f9b8)" waittime="3468" ownerId="70716" transactionname="user_transaction" lasttranstarted="2023-02-19T22:11:24.247" XDES="0x2d6a7284428" lockMode="S" schedulerid="9" kpid="7124" status="suspended" spid="65" sbid="0" ecid="0" priority="0" trancount="1" lastbatchstarted="2023-02-19T22:11:24.247" lastbatchcompleted="2023-02-19T22:11:24.247" lastattention="1900-01-01T00:00:00.247" clientapp="Microsoft SQL Server Management Studio - Query" hostname="DESKTOP-STS8TPB" hostpid="1696" loginname="DESKTOP-STS8TPB\Administrator" isolationlevel="read committed (2)" xactid="70716" currentdb="7" currentdbname="Northwind" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
    <executionStack>
     <frame procname="adhoc" line="5" stmtstart="26" stmtend="118" sqlhandle="0x02000000dd7720067e0519b8a368501716c04b4b50cfe6be0000000000000000000000000000000000000000">
unknown     </frame>
     <frame procname="adhoc" line="5" stmtstart="196" stmtend="282" sqlhandle="0x0200000093f01512208755a056f5f28930fbd3dedf58a2850000000000000000000000000000000000000000">
unknown     </frame>
    </executionStack>
    <inputbuf>

BEGIN TRAN
UPDATE dbo.Employees SET Title='Dr.' WHERE EmployeeID=1;
WAITFOR DELAY '00:00:10'
SELECT * FROM dbo.Orders WHERE OrderID=10258
ROLLBACK
    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057594043170816" dbid="7" objectname="Northwind.dbo.Employees" indexname="PK_Employees" id="lock2d69ccbbb80" mode="X" associatedObjectId="72057594043170816">
    <owner-list>
     <owner id="process2d6ae694ca8" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process2d69c9748c8" mode="S" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057594044088320" dbid="7" objectname="Northwind.dbo.Orders" indexname="PK_Orders" id="lock2d69ccbbf80" mode="X" associatedObjectId="72057594044088320">
    <owner-list>
     <owner id="process2d69c9748c8" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process2d6ae694ca8" mode="S" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

虽然上面有图形化表示,但在生产环境下参考价值并不多,因为这张图蕴含的信息比较少,熟读和整理 xml 的内容就非常必要了,截图如下:

SQLSERVER 语句交错引发的死锁研究_死锁_04

仔细观察上面的这张图可以清晰的看到,spid=66 持有了 Orders.PK_Orders 索引上哈希码为 59ce0997f9b8 键值的 X 锁,之后需要再次获取 Employees.PK_Employees 索引上哈希码为 8194443284a0 键值上的 S 锁,很不巧的是,此时的 Employees.PK_Employees 索引上哈希码为 8194443284a0 的键值已经被 spid=65 的会话附加了 X 锁,这是一种典型的相互等待造成的死锁。

同时也可以观察到,我们的语句是一个 adhoc 即时查询,其外层也没有 存储过程 之类的包围语句。

3. 寻找解决方案

知道了是什么语句和什么语句之间的冲突之后,后面的问题就比较简单了,常见措施如下:

  • 使用 nolock 脏读

由于冲突中涉及到了 S 锁,其实绝大多数系统对脏读不是特别敏感,所以使用 nolock 无锁提示是一个好办法。

BEGIN TRAN
UPDATE  dbo.Orders SET  ShipAddress='上海' WHERE OrderID=10258
WAITFOR DELAY '00:00:10'
SELECT * FROM dbo.Employees WITH(NOLOCK) WHERE EmployeeID=1;
ROLLBACK


BEGIN TRAN
UPDATE dbo.Employees SET Title='Dr.' WHERE EmployeeID=1;
WAITFOR DELAY '00:00:10'
SELECT * FROM dbo.Orders WITH(NOLOCK) WHERE OrderID=10258
ROLLBACK
  • 使用 MVCC 多版本控制

现代化的关系型数据库都支持 快照读 来解决 并发读写 的冲突,同时又能保证不脏读,简而言之就是在事务修改时将修改前的数据存到 tempdb 中来形成字段的版本化。

首先需要从 数据库 级别开启它。

ALTER DATABASE Northwind SET ALLOW_SNAPSHOT_ISOLATION ON

然后在各自事务中显式使用 SNAPSHOT 隔离级别查询,参考sql如下:

-- 会话 65 --
SET TRAN ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
UPDATE dbo.Employees SET Title='Dr.' WHERE EmployeeID=1;
WAITFOR DELAY '00:00:10'
SELECT * FROM dbo.Orders WHERE OrderID=10258
ROLLBACK

-- 会话 66 --
SET TRAN ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
UPDATE  dbo.Orders SET  ShipAddress='上海' WHERE OrderID=10258
WAITFOR DELAY '00:00:10'
SELECT * FROM dbo.Employees  WHERE EmployeeID=1;
ROLLBACK

三:总结

在真实的死锁案例集锦中,相对来说 语句顺序交错 引发的死锁会相对多一些,其次就是 书签查找,这个放到后面的文章中来聊,面对 语句顺序交错 的场景尽量的收集整理死锁的 xml数据,或许有很多意想不到的发现。



标签:语句,00,SET,dbo,Employees,SQLSERVER,死锁,WHERE
From: https://blog.51cto.com/u_15353947/6162284

相关文章

  • 使用SqlBulkCopy sqlserver 批量插入数据(C#)
    先说实现原理:将需要插入数据库的集合里面的数据存在datatable里面,列名需相同,然后调用SqlBulkCopy方法。1.新建datatable,并赋值DataTabledataTable=newDataTable();dataTable.Columns.Add("NAME1");dataTable.Columns.Add("NAME2");dataTable.Columns.Add("NAME3");data......
  • 根据DataTable,生成建表语句
    点击查看代码///<summary>///根据DataTable,生成建表语句///</summary>///<paramname="table"></param>///<paramname="tableName"></param>///<returns><......
  • 12 SQL语句的补充----复制表数据
    复制数据表1、复制数据1.1创建一个字段和源表不同的虚拟表 createtemporarytablet_yy( idint(16), namevarchar(25), sexvarchar(25) );源表与目标字段不一致,复制数据需要指定字段insertintot_yy(id,name,sex)selectid,sname,sexfromt_student;1.2创建一......
  • Mysql之SQL语句基础1
    一、基本概念——后续的内容将会记录作者在计科学习内容DB(数据库):存储数据的仓库,数据是有组织进行存储DBMS(数据库管理系统):操纵和管理数据库的大型软件SQL:操纵关系数据库的编程语言,是一套标准有Mysql,Oracle,SQLSever,PostgreSQlRDBMS(关系型......
  • 全网最详细中英文ChatGPT-GPT-4示例文档-类比语句智能生成从0到1快速入门——官网推荐
    目录Introduce简介setting设置Prompt提示Sampleresponse回复样本APIrequest接口请求python接口请求示例node.js接口请求示例curl命令示例json格式示例其它资料下载ChatGPT是目前最先进的AI聊天机器人,它能够理解图片和文字,生成流畅和有趣的回答。如果你想跟上AI时代的潮流......
  • 流程控制和循环语句
             ......
  • 注释、变量和常量、输入输出语句
      ......
  • 循环语句
    3.循环语句while循环引入不知道大家是否喜欢下象棋,反正楼下大爷还是挺喜欢的~如果这盘棋下完了,可以收拾一下接着重来一局,这重来一局说到底就是重复软件中也是需要这样的功能的,像打游戏(这个大家都有经验就不过多说了)时这局没吃到"鸡",可以再重新玩一局对嘛,这也是重复程序中......
  • 死锁(deadlock)
    线程死锁的必备要素互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待;不可剥夺条......
  • MySql基本的简单sql语句
    SQL语句分类必须记住删除语句DELETEFROM表名WHERE条件插入语句简单的插入语句INSERTINTO表名(字段列表)VALUES(值列表)查询出的数据插入到已存在的表中查询出的数......