一次查找sqlserver死锁的经历
查找bug是程序员的家常便饭,我身边的人喜欢让用户来重现问题。当然他们也会从正式服务器上下载错误log,然后尝试分析log,不过当错误不是那种不经思考就可识别的情况,他们就会将问题推向用户,甚至怪罪程序依赖的平台。他们常用的借口就是“这个问题很难重现,需要持续监控,而且不知道要监控几天”。下次出现,同样是这个说法。
编程珠玑一书的作者说,“对付问题而不是程序”,这是方向。程序员一旦有了方向就是全世界最聪明的人,反之则会用最聪明的头脑做最蠢的事情,说最蠢的话。查找错误的方向就是基于科学的方法理解问题、解决问题。
查找错误的一般方法
- 列出问题出现的可能原因(清单)。
- 针对问题发现的可能性进行排序清单。
- 假设问题就如清单其中一项所述,然后(编写程序)去重现问题。
- 循环3,并且在不可能出现的行画上一条删除线。
你可以说,这是穷举法。以下是查找问题的过程简述。
问题描述
前几天被突然叫到会议室,老板让暂停所有的工作,优先处理一个紧急问题。然后屏幕上出现了以下的一段log:
| “事务(进程 ID 78)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: 事务(进程 ID 78)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。” (后面的几乎类似,除了ID编号和时间,所以就省略了) |
看完了log,老板接着说明这个问题的紧急性--系统正处于验收阶段。
列出可能的清单
针对这个问题,我google了一下,发现大部分文章描述sqlserver的死锁时,经常谈到两种场景。
场景1:
|
事务A: delele from table1 where id= 1; select * from table2; 事务B: delele from table2 where id= 1; select * from table1; |
要理解这个场景,首先要了解两种锁的区别:X锁和S锁。简单的说,就是当试图删除、更新表时,我们会在该表上加一个X锁,这个X锁是排他锁,影响是除非表上的X锁被释放,不然其它人无法再加如何锁;而S锁是一个共享锁,它允许同一时间有多个共享锁存在,但是除了X锁以外。
假设场景1是并行执行的,事务1开始对table1加上了X锁,同时事务2也对table2加上了X锁。接下来它们又分别试图请求,在对方已经加上了X锁的表上加上S锁。从前面得知,X锁是排他锁,而事务的特性是,所有的锁资源都会在事务完成以后才释放。 这时候事务间各自拥有对方请求的资源,又同时请求对方拥有的资源,并且要释放自身的资源,先要请求到对方的资源,就发生死锁了。
场景2:
| 事务A:delete from table1 where id =(1,..,n); 事务B:select c1,c2,c3 from table1 where id = (1,..,n); table1的索引如下,物理索引id,逻辑索引c1,c2。 |
据说这个场景的死锁比较普遍,但是很难理解,因为它们由始至终只操作一个表。要理解这个,还需从sqlserver中的bookmark search说起:如果select 语句中,查询的栏位不包含在逻辑索引中,比如c3,那么sqlserver将试图在使用物理索引id来查找c3的值。
现在再来看一下过程中发生的锁,select语句会在逻辑索引(c1,c2)上加上S锁,然后为了返回不在逻辑索引的栏位(c3),它还需要在物理索引(id)上加上S锁;而更新呢?我们都知道,更新表时,我们需要对物理索引(id)加上排他锁以完成表的更新,并且随后还会被要求更新逻辑索引(c1,c2)。所以事务A先请求对id列的X锁,随后请求c1,c2列的X锁;而事务B会先请求c1,c2的S锁,接着请求id的S锁来返回c3的值。死锁就这样发生了。
幸亏这个场景发生的前提是频繁查询以及频繁更新表。
至此,我们的清单中有了两项场景1与场景2。根据2/8原则,先尝试最有可能的往往事半功倍。我随之放弃了猜测,马上进入验证阶段。开始先给它们排个序,我将场景1放在第一项,原因是它比场景2更容易被发现。因为场景2的特点是随机,即使有这样的两个事务并发(这种情况太普遍了),死锁也是很难出现。
重现问题
假设清单第一项(场景1)是问题的原因,那么它应该是这样的:系统中肯定存在这样的两个事务,一个事务对产生死锁的表(暂时称为deadlockedtable)读取,并且对另外的表(暂时称为causelockedtable)更新;而另一事务对deadlockedtable更新,对causelockedtable读取。
以此,我查找了项目的所有事务代码,没有找到。
接下来,我在这个选项上划上一条删除线。
开始选择清单的下一项,然后继续
关于场景2在系统中的确存在,这要从系统的业务谈起,出现错误的系统是一个考勤管理系统,客户公司有大约2000人使用系统,系统每天凌晨都会计算2000人当天的考勤排配表。由于公司严格的考勤机制,员工上班前都会查看自己当天的考勤排配表;对于那些请假、出差的员工,他们在系统中请假被批准后,系统需要重新计算其考勤。这里一直提到考勤结果,是因为出现死锁的表就是考勤结果表,而出现死锁的时间刚好为员工上班高峰期及凌晨计算全公司员工排配表两个时段,并且很随机及短暂。
这和场景2描述的很类似--频繁更新和查询。
问题时如何重新这个异常。
2000人的公司,由于三班倒的机制,我按照大约只有二分之一的员工在某个点上班,1/10的并发几率,100个并发。该公司的员工总有5%左右的人需要申请请假、调休或者出差等等。他们经常被统一时间批准申请。所以计算的并发可能为50。
模拟代码如下
|
public class PerformanceTest extends BaseTestCase { private int logLevel = 0; public void setLogLevel(int i) { private static class TaskStats { class PerfTask implements Callable<TaskStats> { public void runOnce() { long rbegin = System.currentTimeMillis(); long rend = System.currentTimeMillis(); long bend = System.currentTimeMillis(); Thread.yield(); @Override private void run(int nrIterations, int nrThreads) { ExecutorService threadPool = Executors.newFixedThreadPool(nrThreads); List<Callable<TaskStats>> tasks = new ArrayList<Callable<TaskStats>>(); if (logLevel >= 1) { if (logLevel >= 1) { System.out.println("started"); } if (logLevel >= 1) { System.out.println("go"); } if (logLevel >= 1) { System.out.println("finish"); } TaskStats aggregate = new TaskStats(); System.out.println("-----------------------------------------"); threadPool.shutdown(); @Test |
为了尽可能的接近真实情况,这个代码最好在两台以上的电脑上运行,并且要注意避免sql共享机制,这点可以让参数随机产生。
程序跑了10分钟后,死锁果真出现了,如log错误描述一般。
一次查找sqlserver死锁的经历的更多相关文章
- 查找 SqlServer死锁
use master if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_who_lock]') ) dr ...
- sqlserver 死锁原因及解决方法
其实所有的死锁最深层的原因就是一个:资源竞争 表现一: 一个用户A 访问表A(锁住了表A),然后又访问表B,另一个用户B 访问表B(锁住了表B),然后企图访问表A,这时用户A由于用户B已经锁住表B,它 ...
- 模拟 SQLSERVER 死锁
环境: sqlserver 2008 事务(进程 ID (n))与另一个进程被死锁在锁资源上,并且已被选作死锁牺牲品.请重新运行 死锁原理: 如两个任务 任务1,已经锁定R1,再进行请求R2& ...
- SqlServer死锁与阻塞检测脚本
IF EXISTS (SELECT * FROM sysobjects WHERE [name] = 'sp_Lock_Scan') DROP PROCEDURE sp_Lock_Scan GO CR ...
- sqlserver 死锁查看辅助存储过程
USE [master] GO /****** Object: StoredProcedure [dbo].[sp_who_lock] Script Date: 03/23/2016 14:17:49 ...
- SQLSERVER 死锁标志
最开始做DBA的时候,整天死锁到头痛1222,至今都能回想到这个错误窗口: 死锁定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待 ...
- 验证SQLServer死锁进程
SELECT '现在没有阻塞和死锁信息' AS message -- 循环开始WHILE @intCounter <= @intCountProperties BEGIN-- 取第一条记录 SE ...
- sqlserver 死锁相关
参考 https://www.cnblogs.com/fuyuanming/p/5783421.html -- 查询死锁 select request_session_id spid, OBJECT_ ...
- 查找sqlserver数据库中,查询某值所表名和字段名
有时候我们想通过一个值知道这个值来自数据库的哪个表以及哪个字段,通过一个存储过程实现的.只需要传入一个想要查找的值,即可查询出这个值所在的表和字段名. 前提是要将这个存储过程放在所查询的数据库. CR ...
随机推荐
- 【随笔】 Win7下安装Git
Git GGit是一个开源的分布式版本控制系统,用以有效.高速的处理从很小到非常大的项目版本管理.[2] Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源 ...
- Android如何避免输入法弹出时遮挡住按钮或输入框
在AndroidManifest.xml中为对应的activity添加android:windowSoftInputMode="adjustResize" 在AndroidMani ...
- Visual Studio中判断项目的类型
1. 打开项目的属性,查看Application的Output type
- Parcel Vs Webpack
横空出世的Parcel近日成为了前端圈的又一大热点,在短短几周内就获得了13K的Star.作为前端构建工具新人的Parcel为什么能在短期内获得这么多赞同?他和老大哥Webpack比起来到底有什么优势 ...
- centos7安装java开发环境
一. 安装jdk 1.进入oracle官网下载jdk-8u152-linux-x64.tar.gz,用WinScp将文件上传到/usr/local文件下 2.解压:执行命令 tar –xzvf jdk ...
- eclipse中php项目开发的环境配置说明
PHP开发的环境配置比Java开发要简单点,也就是我们不用安装jdk了,我们不用安装tomcat了,仅仅通过一种集成环境来安装就好了. PHP开发,其实有很多种环境配置方式,我这里使用了XAMPP进行 ...
- 6、springboot之根目录设置
访问的时候用
- poj 1141 Brackets Sequence ( 区间dp+输出方案 )
http://blog.csdn.net/cc_again/article/details/10169643 http://blog.csdn.net/lijiecsu/article/details ...
- Android xmlns 的作用及其自定义
转自:http://blog.csdn.net/chuchu521/article/details/8052855 xmlns:Android="http://schemas.android ...
- 配置zookeeper集群
创建3台服务,不同ip,相同端口 1.先安装jdk1.8 解压: tar -zxvf jdk-8u11-linux-x64.tar.gz 重新命名文件夹名字: mv jdk1..0_11/ jdk8 ...