SQL Server死锁中的会话隔离级别为序列化(Serializable)实验测试
最近在分析SQL Server的死锁时,发现一个比较有意思的现象,发现死锁当中一个会话的隔离级别为序列化(Serializable),这个是让人比较奇怪的地方,我们知道SQL Server数据库的默认隔离级别为已提交读(READ COMMITTED),除非人为设置事务隔离级别(TRANSACTION ISOLATION LEVEL),否则事务隔离级别会使用数据库的默认隔离级别。在分析了死锁相关的存储过程后,没有发现有人为修改事务隔离级别的地方。在分析过后,我们判断应该是在应用程序代码里面有设置隔离级别,下面我们通过一个小实验来构造这样的一个案例。
测试环境数据库为AdventureWorks2014,如下所示,我简单写了一点C#代码,截取黏贴部分C#代码在此,在这段代码中,我们使用TransactionScope,我们先更新Sales.SalesOrderDetail,然后查询 [Sales].[SalesOrderHeader]的相关数据来绑定Grid控件
try
{
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection(connString))
{
string cmdText = "UPDATE Sales.SalesOrderDetail SET OrderQty=2 WHERE SalesOrderID=43659 AND SalesOrderDetailID=1;";
SqlCommand cmd = new SqlCommand(cmdText, conn);
conn.Open();
cmd.ExecuteNonQuery();
}
using (SqlConnection conn = new SqlConnection(connString))
{
DataSet sqldataset = new DataSet();
string cmdText = "SELECT * FROM [Sales].[SalesOrderHeader] WHERE SalesOrderID=43659;";
SqlCommand cmd = new SqlCommand(cmdText, conn);
SqlDataAdapter sqladapter = new SqlDataAdapter(cmdText, conn);
sqladapter.Fill(sqldataset, "spt_values");
gvData.DataSource = sqldataset;
gvData.DataBind();
}
scope.Complete();
}
}
catch (TransactionAbortedException exc)
{
log.Error("错误", exc);
}
然后另外一个会话,就直接用SSMS开启一个事务(懒得构造C#代码案例,主要是太浪费时间了),主要执行下面逻辑:
BEGIN TRAN
UPDATE [Sales].[SalesOrderHeader] SET SubTotal = SubTotal + 10
WHERE SalesOrderID=43659;
WAITFOR DELAY '00:00:10';
SELECT TOP 10 * FROM Sales.SalesOrderDetail
--ROLLBACK TRAN;
执行上面SQL语句,然后运行最上面C#代码,立马就能构造出一个死锁案例,如下截图所示,测试环境为SQL Server 2014,我就使用扩展事件system_health捕获的死锁(当然,你可以使用任何方式,例如Profile或Trace捕获死锁相关信息),使用SQL将死锁的XML信息查出

如下所示,你会看到使用TransactionScope的会话的隔离级别为isolationlevel="serializable (4)", 具体可以参考下面死锁的XML文件。


<deadlock>
<victim-list>
<victimProcess id="process17676e108" />
</victim-list>
<process-list>
<process id="process17676e108" taskpriority="0" logused="384" waitresource="KEY: 7:72057594048479232 (0ca7b7436f59)" waittime="379" ownerId="46635671" transactionname="user_transaction" lasttranstarted="2019-04-02T23:26:21.150" XDES="0x17f0511f0" lockMode="S" schedulerid="1" kpid="13440" status="suspended" spid="61" sbid="0" ecid="0" priority="0" trancount="1" lastbatchstarted="2019-04-02T23:26:21.147" lastbatchcompleted="2019-04-02T23:26:09.343" lastattention="1900-01-01T00:00:00.343" clientapp="Microsoft SQL Server Management Studio - Query" hostname="MyNB00021" hostpid="9728" loginname="test" isolationlevel="read committed (2)" xactid="46635671" currentdb="7" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
<executionStack>
<frame procname="adhoc" line="8" stmtstart="282" stmtend="368" sqlhandle="0x020000002a285923f5e38f7347b53337195c56a4a1bc33080000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
BEGIN TRAN
UPDATE [Sales].[SalesOrderHeader] SET SubTotal = SubTotal + 10
WHERE SalesOrderID=43659;
WAITFOR DELAY '00:00:10';
SELECT TOP 10 * FROM Sales.SalesOrderDetail </inputbuf>
</process>
<process id="process175603c28" taskpriority="0" logused="436" waitresource="KEY: 7:72057594048544768 (6a8a6db47ef5)" waittime="4420" ownerId="46635065" transactionname="user_transaction" lasttranstarted="2019-04-02T23:25:36.807" XDES="0x1762fa9f0" lockMode="S" schedulerid="1" kpid="51760" status="suspended" spid="63" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2019-04-02T23:26:26.450" lastbatchcompleted="2019-04-02T23:25:36.807" lastattention="1900-01-01T00:00:00.807" clientapp=".Net SqlClient Data Provider" hostname="MyNB00021" hostpid="1700" loginname="kkk" isolationlevel="serializable (4)" xactid="46635065" currentdb="7" lockTimeout="4294967295" clientoption1="673316896" clientoption2="128056">
<executionStack>
<frame procname="AdventureWorks2014.Sales.iduSalesOrderDetail" line="18" stmtstart="982" stmtend="2448" sqlhandle="0x0300070076146e6c18e00a016ba3000000000000000000000000000000000000000000000000000000000000">
INSERT INTO [Production].[TransactionHistory]
([ProductID]
,[ReferenceOrderID]
,[ReferenceOrderLineID]
,[TransactionType]
,[TransactionDate]
,[Quantity]
,[ActualCost])
SELECT
inserted.[ProductID]
,inserted.[SalesOrderID]
,inserted.[SalesOrderDetailID]
,'S'
,GETDATE()
,inserted.[OrderQty]
,inserted.[UnitPrice]
FROM inserted
INNER JOIN [Sales].[SalesOrderHeader]
ON inserted.[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID </frame>
<frame procname="adhoc" line="1" stmtstart="52" stmtend="262" sqlhandle="0x02000000abf4ee0ff24fea415c6f35709c721203030a173b0000000000000000000000000000000000000000">
unknown </frame>
<frame procname="adhoc" line="1" stmtend="186" sqlhandle="0x02000000b0cd40243d43ed1a51b1baa9cbf70d1628eae7880000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
UPDATE Sales.SalesOrderDetail SET OrderQty=2 WHERE SalesOrderID=43659 AND SalesOrderDetailID=1; </inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594048479232" dbid="7" objectname="AdventureWorks2014.Sales.SalesOrderDetail" indexname="PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID_old" id="lock154ffb300" mode="X" associatedObjectId="72057594048479232">
<owner-list>
<owner id="process175603c28" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process17676e108" mode="S" requestType="wait" />
</waiter-list>
</keylock>
<keylock hobtid="72057594048544768" dbid="7" objectname="AdventureWorks2014.Sales.SalesOrderHeader" indexname="PK_SalesOrderHeader_SalesOrderID" id="lock155a8fa00" mode="X" associatedObjectId="72057594048544768">
<owner-list>
<owner id="process17676e108" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process175603c28" mode="S" requestType="wait" />
</waiter-list>
</keylock>
</resource-list>
</deadlock>
我们也可以使用下面SQL语句来捕获会话的的隔离级别(根据实际情况调整), 在运行上面C#代码期间捕获会话信息,如下截图所示:
DECLARE @end_time DATETIME;
SET @end_time = DATEADD(SECOND, 10, GETDATE());
WHILE GETDATE() < @end_time
BEGIN
INSERT INTO mintor_isolcation_level
SELECT session_id ,
start_time ,
status ,
total_elapsed_time ,
CASE transaction_isolation_level
WHEN 1 THEN 'ReadUncomitted'
WHEN 2 THEN 'ReadCommitted'
WHEN 3 THEN 'Repeatable'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot'
ELSE 'Unspecified'
END AS transaction_isolation_level ,
sh.text ,
ph.query_plan
FROM sys.dm_exec_requests
CROSS APPLY sys.dm_exec_sql_text(sql_handle) sh
CROSS APPLY sys.dm_exec_query_plan(plan_handle) ph
END
因为上面的脚本执行时间太短,所以有可能捕获到的是相关SQL运行期间的触发器脚本。如果要清晰的捕获相关SQL,可以构造一个执行时间较长的SQL

是否有点意外,其实官方文档已有详细介绍(详见参考资料),摘抄部分信息如下,TransactionScope如果不指定隔离级别,默认情况下,事务隔离级别为Serializable
设置 TransactionScope 隔离级别
除超时值之外,TransactionScope 的有些重载构造函数还接受 TransactionOptions 类型的结构,用于指定隔离级别。 默认情况下,事务在隔离级别设置为 Serializable 的情况下执行。 通常对频繁执行读取的系统选择 Serializable 之外的隔离级别。 这需要全面地了解事务处理理论、事务本身的语义、所涉及的并发问题以及系统一致性的结果。
总结
这里只是一个案例,仅仅说明应用程序的驱动程序或API函数,有可能会需要(或默认)设定事务的隔离级别,这个一定要当心,避免由于人为失误导(不了解技术细节)致不小心提高事务隔离级别,造成不必要的死锁出现。另外,这里总结这篇文章,也仅仅是对这种案例感到有意思。
参考资料:
https://docs.microsoft.com/en-us/dotnet/framework/data/transactions/implementing-an-implicit-transaction-using-transaction-scope
SQL Server死锁中的会话隔离级别为序列化(Serializable)实验测试的更多相关文章
- 转载 SQL Server 2008 R2 事务与隔离级别实例讲解
原文:http://blog.itpub.net/13651903/viewspace-1082730/ 一.事务简介 SQL Server的6个隔离级别中有5个是用于隔离事务的,它们因而被称作事务隔 ...
- oracle,mysql,sql server三大数据库的事务隔离级别查看方法
1:mysql的事务隔离级别查看方法 mysql 最简单,执行这条语句就行:select @@tx_isolation 详情: 1.查看当前会话隔离级别 select @@tx_isolation; ...
- SQL Server事务的四种隔离级别
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些是在事务内和事务间可见的,哪些是不可见的.较低级别的隔离通常可以执行更高的并发,系统的开销也更低. 1.未提交读(Read ...
- SQL Server 内存中OLTP内部机制概述(三)
----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory ...
- 在SQL Server 2008中执行透明数据加密
问题 安全是任何公司的一个主要考量.数据库备份容易被偷并被恢复到另一个SQL Server实例上.当我们浏览SQL Server 2008的新特性时,我们对一个叫做透明数据加密的特性很感兴趣,我们可以 ...
- Sql Server中的事务隔离级别
数据库中的事物有ACID(原子性,一致性,隔离性,持久性)四个特性.其中隔离性是用来处理并发执行的事务之间的数据访问控制.SqlServer中提供了几种不同级别的隔离类型. 概念 Read UnCom ...
- Innodb中的事务隔离级别和锁的关系
前言: 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式.同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力. ...
- Innodb中的事务隔离级别和锁的关系(转)
原文:http://tech.meituan.com/innodb-lock.html 前言: 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式.同时数据库 ...
- MySQL InnoDB中的事务隔离级别和锁的关系
前言: 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式.同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力. ...
随机推荐
- 构建基于Netty 的HTTP/HTTPS 应用程序
HTTP/HTTPS是最常见的协议套件之一,并且随着智能手机的成功,它的应用也日益广泛,因为对于任何公司来说,拥有一个可以被移动设备访问的网站几乎是必须的.这些协议也被用于其他方面.许多组织导出的用于 ...
- Python基础之数据类型、变量、常量
数据类型 整数:任意大小的整数,十六进制用0x前缀 浮点数:浮点数也就是小数,科学计数法1.23x109就是1.23e9,0.000012可以写成1.2e-5 字符串:以单引号'或双引号"括 ...
- Fedora Linux安装deb包
前言: 我们知道,在Linux系统上有两种主要的软件包格式,分别是RPM和deb. RPM是"RPM Package Manager(RPM软件包管理器)"的递归缩写.RPM是一种 ...
- JS题目合集---新技术层出不穷,打好基础才是上策~
在IT界中公司对JavaScript开发者的要求还是比较高的,但是如果JavaScript开发者的技能和经验都达到了一定的级别,那他们还是很容易跳到优秀的公司的,当然薪水就更不是问题了.但是在面试之前 ...
- win7 telnet命令无法开启的解决方案(不是内部命令或外部命令)
如果你想在win 7上直接使用 telnet命令,却不能开启那怎么办呢?记得在Wingdows XP上telnet都是已经安装好的,直接就可用,但是Win7是没有这个功能的,都需要后来自己安装的,下面 ...
- 接口测试返回的json文件中字符串是乱序
问题描述 接口测试中post方式匹配返回信息时显示不匹配, 但是statuscode明明是200, 而且用postman /restclient等工具测出来也是没问题的. 根本原因 封装了这么个方法来 ...
- Docker 新手入门
简介 如果您是 Docker 新手请您花大约三十分钟的时间来了解 Docker 相关的知识和内容. Docker 与 Linux 息息相关,因此在阅读本文档之前请您确保以下条件: 对 Linux 的命 ...
- bzoj 1098 poi2007 办公楼 bfs+链表
题意很好理解,求给出图反图的联通块个数. 考虑这样一个事情:一个联通块里的点,最多只会被遍历一次,再遍历时没有任何意义 所以用链表来存,每遍历到一个点就将该点删掉 #include<cstdio ...
- bzoj 2653 middle 二分答案 主席树判定
判断中位数是否可行需要将当前的解作为分界,大于其的置为1,小于为-1,然后b-c必选,ab,cd可不选,这个用线段树判定就好 但不能每次跑,所以套主席树,按权值排序,构建主席树,更新时将上一个节点改为 ...
- BZOJ_1858_[Scoi2010]序列操作_线段树
BZOJ_1858_[Scoi2010]序列操作_线段树 Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询 ...