每天10分钟学习事务与并发(Part 4)

【数据库技术 作者  / Edison Zhou

这是 恰童鞋骚年 的第 181 篇原创文章

上一篇 介绍了在MS SQL Server中事务的六个隔离级别 中的后三个: 可序列化、快照 和  已提交读隔离最后还对六种隔离级别做一个总结。 本篇介绍死锁及其实例,最后告诉你如何避免死锁。

1 死锁是个什么鬼

死锁是指一种 进程之间互相永久阻塞的状态 ,可能涉及到两个或者多个进程。 两个进程发生死锁的例子是: 进程A阻塞了进程B,进程B又阻塞了进程A。 在任何一种情况下,SQL Server都可以检测到死锁,并选择终止其中一个事务以干预死锁状态。 如果SQL Server不干预,那么死锁涉及到的进程将会永远保持死锁状态。

默认情况下,MS SQL Server会选择终止做过的操作最少的事务,因为这样可以让回滚开销降低到最低 当然,在SQL Server 2005及之后的版本中,可以通过将会话选项DEADLOCK_PRIORITY设置为范围(-10到10)之间的任一整数值。

2 死锁实例

这里示例仍然打开三个会话: Connection A、B和C:

Step1. 在Connection A中更新Products表中产品2的行记录,并保持事务一直打开:

-- Connection A

USE TSQLFundamentals2008;


BEGIN TRAN;


UPDATE Production.Products

SET unitprice = unitprice + 1.00

WHERE productid = 2;

这时Connection A对产品表的产品2请求了排它锁。

Step2. 在Connection B中更新OrderDetails表中产品2的订单明细,并保持事务一直打开:

-- Connection 2

BEGIN TRAN;


UPDATE Sales.OrderDetails

SET unitprice = unitprice + 1.00

WHERE productid = 2;

这时Connection A对订单明细表的产品2请求了排它锁。

Step3. 回到Connection A中,执行以下语句,请求查询产品2的订单明细记录:

-- Connection A


SELECT orderid, productid, unitprice

FROM Sales.OrderDetails

WHERE productid = 2;


COMMIT TRAN;

由于此时实在默认的READ COMMITED隔离级别下运行的,所以Connection A中的事务需要一个共享锁才能读数据,因此这里会一直阻塞住。 但是,此时并没有发生死锁,而只是发生了阻塞。

Step4. 回到Connection B中,执行以下语句,尝试在Products表查询产品2的记录:

-- Connection 2


SELECT productid, unitprice

FROM Production.Products

WHERE productid = 2;


COMMIT TRAN;

这里由于这个请求和Connection A中的事务在同一个资源上持有的排它锁发生了冲突,于是相互阻塞发生了死锁。 SQL Server通常会在几秒钟之内检测到死锁,并从这两个进程中选择一个作为牺牲品,终止其事务。 所以我们还是得到了以下结果:

Step5. 刚刚提到了SQL Server会选择一个作为牺牲品,我们回到Connection A会看到以下的错误信息提示:

在这个例子中,由于两个事务进行的工作量差不多一样,所以任何一个事务都有可能被终止。 (前面提到,如果没有手动设置优先级,那么SQL Server会选择工作量较小的一个事务作为牺牲品)另外, 解除死锁需要一定的系统开销,因为这个过程会涉及撤销已经执行过的处理

显然,事务处理的时间越长,持有锁的时间也就越长,死锁的可能性也就越大。 应该尽量保持事务简短 ,把逻辑上可以属于同一工作单元的操作移到事务之外

3 如何避免死锁

改变访问资源的顺序可以避免死锁

继续上面的例子,Connection A先访问Products表中的行,然后访问OrderDetails表中的行; Connection B先访问OrderDetails表中的行,然后访问Products表中的行。

这时如果我们改变一下访问顺序: 两个事务按照同样的顺序来访问资源,则不会发生这种类型的死锁。

通过交换其中一个事务的操作顺序,就可以避免发生这种类型的死锁(假设交换顺序不必改变程序的逻辑)。

良好的 索引设计也可以避免死锁

如果查询筛选条件缺少良好的索引支持,也会造成死锁。 例如,假设Connection B中的事务有两条语句要对产品5进行筛选,Connection A中的事务要对产品2进行处理,那么他们就不应该有任何冲突。 但是,如果在表的productid列上如果没有索引来支持查询筛选,那么MS SQL Server就必须扫描(并锁定)表中的所有行,这样当然会导致死锁。

总之,良好的索引设计将有助于减少这种没有真正的逻辑冲突的死锁。

最后,按照国际惯例清理掉测试数据:

-- Cleanup

UPDATE Production.Products

SET unitprice = 19.00

WHERE productid = 2;


UPDATE Sales.OrderDetails

SET unitprice = 19.00

WHERE productid = 2

AND orderid >= 10500;


UPDATE Sales.OrderDetails

SET unitprice = 15.20

WHERE productid = 2

AND orderid < 10500;

4 小结

本文 介绍了 死锁及其实例,以及告诉你如何避免死锁。 到此,每天10分钟学习T-SQL及事务与并发的系列文章就结束了,相信你一定 学有所获

5 参考资料

[美] Itzik Ben-Gan 著,成保栋 译,《Microsoft SQL Server 2008技术内幕:T-SQL语言基础》

想要构建 高性能服务 应对高并发?

「  校宝在线高级架构师肖伟宇老师在极客时间推出了一门 .NET Core开发实战 课程,专注于讲述 ASP.NET Core+微服务+K8S这个主题 ,为你讲述各种最佳实践和使用经验,有兴趣的话可以通过 扫描海报下方的二维码订阅 ,开始你的学习之路!」

使用优惠口令「 dotnet123

:point_down: 原价 ¥129 ,使用优惠口令立减 10 元!

△扫码免费试看课程

往期 精彩 回顾

【推荐】.NET Core开发实战视频课程   ★★★

.NET Core on K8S学习实践系列文章索引

.NET Core 微服务学习实践系列文章索引

【资料】2019 .NET China Conf 大会资料下载

【视频】2019 .NET China Conf 大会 视频发布

.NET China Conf 路一直都在,社区会更好

基于Jenkins的开发测试全流程持续集成实践

基于Jenkins的ASP.NET Core持续集成实践

.NET单元测试的艺术系列文章索引目录

关于 恰童鞋骚年 公众号

恰童鞋骚年,风华也许不再正茂,

但却仍想挥斥方遒

本公众号会长期关注和分享.NET Core、微服务、云原生、DevOps 及 企业数字化转型等技术内容文章,还会与你分享个人生活成长的点滴及各类好书的读书笔记,希望能对你有所帮助,一起成长!

长按二维码关注 ,一起学习一起成长

▲  长按关注恰童鞋骚年,成长快人一步

订阅 福利 提醒

后台回复  ppt ,即可获取首届.NET开发者峰会资料

后台回复  docker ,即可获取容器化/K8S学习资料

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章