一次查找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 ...
随机推荐
- 一分钟看懂Docker的网络模式和跨主机通信
文章转载自:http://www.a-site.cn/article/169899.html Docker的四种网络模式Bridge模式 当Docker进程启动时,会在主机上创建一个名为docke ...
- Ubuntu下配置安装Hadoop 2.2
---恢复内容开始--- 这两天玩Hadoop,之前在我的Mac上配置了好长时间都没成功的Hadoop环境,今天想在win7 虚拟机下的Ubuntu12.04 64位机下配置, 然后再建一个组群看一看 ...
- 【wordpress】wordpress初探
接下来,开始wordpress之旅! 访问wordpress文件夹下的index.php 点击现在就开始. 这里要求我们输入数据库名. 所以先去mysql中新建一个wordpress库 create ...
- apache ab测试介绍
apache ab测试介绍 安装ab命令 环境为ubuntu16.04.2 LTS,安装的命令为: sudo apt-get install apache2-utils 使用说明 格式为:ab [op ...
- MySql的备份还原
备份数据是数据库管理最常用的操作.为了保证数据库中数据的安全,数据管理员需要定期进行数据备份.一旦数据库遭到破坏,便可通过备份的文件来还原数据库.因此,数据备份是一项很重要的工作. 数据备份 使用my ...
- Winform取消用户按下键盘的事件
1. 有时候针对某个控件,想取消它的所有键盘按下事件, 只需要为这个控件绑定keyDown事件即可,然后处理的代码如下: private void txtDeviceName_KeyDown(obje ...
- 深入理解Solaris X64系统调用
理解系统调用的关键在于洞悉系统调用号是联系用户模式与内核模式的纽带.而在Solaris x64平台上,系统调用号被保存在寄存器RAX中,从用户模式传递到内核模式.一旦进入内核模式,内核的sys_sys ...
- h5页面ios,双击向上滑动,拖拽到底部还能继续拖拽(露出黑色背景)
h5页面ios,双击向上滑动,拖拽到底部还能继续拖拽 标签: 手机 2016-02-02 18:09 696人阅读 评论(0) 收藏 举报 在ios下,双击屏幕某些地方,滚动条会自动向上走一段. ...
- POj2387——Til the Cows Come Home——————【最短路】
A - Til the Cows Come Home Time Limit:1000MS Memory Limit:65536KB 64bit IO Format:%I64d & ...
- php.ini配置max_execution_time和FPM配置request_terminate_timeout
PHP限定脚本执行时长的方式有几种,下面说下php.ini中的max_execution_time和php-fpm.conf中的request_terminate_timeout 1. php.ini ...