SQLSERVER 语句交错引发的死锁研究
一:背景
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. 一个测试案例
开启两个会话 65
和 66
,分别使用如下查询。
-- 会话 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
两个会话非常简单,交错的对 Employees
和 Orders
进行 SELECT 和 UPDATE 操作,稍等几秒后就会出现死锁。
2. 寻找死锁源头
当我们的应用程序拿到了这样的输出其实作用是不大的,要想溯源最好就是通过不断的对 SQLSERVER 进行监视来捕获死锁时的上下文信息,手段也有很多:
SQL Server Profile
DBCC TRACEON(1222)
DMV VIEW
这里我们就用第一种方式,一定要勾选 TextData
项,因为这里面会有死锁上下文信息的xml表示,截图如下:
将 profile 开启后,重新执行刚才的两个查询,一旦出现死锁,profile 就会成功捕获,然后 copy 出 TextData 项,截图如下:
<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
的内容就非常必要了,截图如下:
仔细观察上面的这张图可以清晰的看到,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数据,或许有很多意想不到的发现。
SQLSERVER 语句交错引发的死锁研究的更多相关文章
- Select语句也会引起死锁
项目上线,准备验收前出现了一个严重的问题:很多select语句作为死锁的牺牲,大部分报表无法打开.这个问题影响范围很大所有的报表都无法访问,而我们的报表是放在电视上面轮播的,电视放在工厂里面,所以出现 ...
- SQLSERVER中的LOB页面简单研究
SQLSERVER中的LOB页面简单研究 这篇文章和我另一篇文章是相辅相成的,在看<SQLSERVER2012 列存储索引的简单研究和测试>这篇文章之前希望大家先看一下这篇文章o(∩_∩) ...
- SQL Server 隐式转换引发的死锁
在SQL Server的应用开发过程(尤其是二次开发)中可能由于开发人员对表的结构不够了解,造成开发过程中使用了不合理的方式造成数据库引擎未按预定执行,以致影响业务.这是非常值得注意的.这次为大家介绍 ...
- 独特的deadlock(仅update语句也能造成死锁)
最近遇到了一个看上去很奇怪,分析起来很有意思的死锁问题.这个死锁看上去难以理解.而分析过程中,又使用了很多分析SQL Server死锁的典型方法.记录下来整个分析过程还是很有意义的. 问题重现步骤: ...
- SQLSERVER语句 in和exists哪个效率高本人测试证明
SQLSERVR语句 in和exists哪个效率高本人测试证明 最近很多人讨论in和exists哪个效率高,今天就自己测试一下 我使用的是客户的数据库GPOSDB(已经有数据) 环境:SQLSERVE ...
- mysql删造成表死锁研究
现在互联网公司对于代码的管理越来越规范,一般都会有3个环境:开发环境,测试环境,正式环境.在开发环境进行开发,开发完成后将代码提交到测试环境进行测试,测试完毕后上线到正式环境. 今天在正式环境上遇到一 ...
- SQLServer语句 汇总
SQL Server语句 序号 功能 语句 1 创建数据库(创建之前判断该数据库是否存在) if exists (select * from sysdatabases where name='data ...
- index_merge引发的死锁排查
概述 前几天排查了一个死锁问题,最开始百思不得其解,因为发生死锁的两个事务是单语句事务,语句类型相同(where属性列相同,仅值不同),而且语句都走了相同的索引,但最终确实发生了死锁.通过定位排查发现 ...
- oracle大数据量更新引发的死锁问题解决方法及oracle分区和存储过程的思考
前言 前几天上午在对数据库的一张表进行操作的时候,由于这张表是按照时间的一张统计表,正好到那天没有测试数据了,于是我想将表中所有的时间,统一更新到后一个月,于是对80w条数据的更新开始了.整个过程曲折 ...
- innodb next-key lock引发的死锁
innodb的事务隔离级别是可重复读级别且innodb_locks_unsafe_for_binlog禁用,也就是说允许next-key lock CREATE TABLE `LockTest` ( ...
随机推荐
- i春秋Hash
打开题目页面是一个超链接 先查看源码得到一段信息 没什么,这只是超链接跳转的信息 点击跳转到下一个界面 提示我们如果我们的传入不是123,123也就是key了,在上面的url就可以看到 如果key!= ...
- 解决manjaro无法连接github问题
修改/etc/hosts文件 1.查看连接ip地址: https://ping.chinaz.com 2.在hosts文件下增加: vim /etc/hosts 需要管理员权限 140.82.113. ...
- 关于解决 inittramfs unpacking failed:Decoding failed 报错
解决办法 vi /etc/initramfs-tools/initramfs.conf 更改COMPRESS=lz4以COMPRESS=gzip 保存更改 sudo update-initramfs ...
- 写一个linux平台的桌面宠物
效果图 前言 我一直在用python 写一下有趣的东西,让编程不那么无聊,之前一直有写一个桌面宠物的想法,无奈这些都是依赖资源文件,没有图片资源没办法写里面的逻辑,直到我看见了 shimiji手机桌面 ...
- Function源码解析与实践
作者:陈昌浩 1 导读 if-else-在代码中经常使用,听说可以通过Java 8的Function接口来消灭if-else-!Function接口是什么?如果通过Function接口接口消灭if-e ...
- 初始化一个ArrayList的多种方式
序言 本片文章用于归纳java中初始化一个ArrayList的几种方式 add方法添加 最基础的方式就是我们在new一个ArrayList之后使用add方法添加元素 /** * 第一种方式,通过add ...
- 详解 Redis 中 big keys 发现和解决
在使用 Redis 时,可能会出现请求响应慢.网络卡顿.数据丢失的情况.排查问题的时候,发现是 big keys 的问题. 什么是 big keys 在 Redis 中,一个字符串类型最大可以达到 5 ...
- TS编写发布订阅模式
interface PubSubType { events: { [key: string]: { name: string, once: boolean, cb: Function }[] } on ...
- kali2021.4a安装angr(使用virtualenv)
在Linux中安装各种依赖python的软件时,最头疼的问题之一就是各个软件的python版本不匹配的问题,angr依赖python3,因此考虑使用virtualenv来安装angr Virtuale ...
- PhaApi NOTORM 实现分表分库
通过自增ID取模要分表的数量,便可得到表名.例如log表分成100张表:log_1,log2...,log100. 每次数据库CURD都先通过获取ID分配到相对应的表,例如:id=66,取模后的结果是 ...