查找bug是程序员的家常便饭,我身边的人喜欢让用户来重现问题。当然他们也会从正式服务器上下载错误log,然后尝试分析log,不过当错误不是那种不经思考就可识别的情况,他们就会将问题推向用户,甚至怪罪程序依赖的平台。他们常用的借口就是“这个问题很难重现,需要持续监控,而且不知道要监控几天”。下次出现,同样是这个说法。

编程珠玑一书的作者说,“对付问题而不是程序”,这是方向。程序员一旦有了方向就是全世界最聪明的人,反之则会用最聪明的头脑做最蠢的事情,说最蠢的话。查找错误的方向就是基于科学的方法理解问题、解决问题。

查找错误的一般方法

  1. 列出问题出现的可能原因(清单)。
  2. 针对问题发现的可能性进行排序清单。
  3. 假设问题就如清单其中一项所述,然后(编写程序)去重现问题。
  4. 循环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;
    private int nrIterations = 5;

public void setLogLevel(int i) {
        logLevel = i;
    }

private static class TaskStats {
        public int waiting = 0;
        public int complete = 0;
        public long totalcalcTime = 0;
        public long totalqueryTime = 0;
        public int nrSamples = 0;
    }

class PerfTask implements Callable<TaskStats> {
        TaskStats taskStats = new TaskStats();
        long calcTime;
        long queryTime;

public void runOnce() {
            try {
                taskStats.waiting++;
                if (logLevel >= 3) {
                    String name = "thread" + Thread.currentThread().getName();
                    System.out.println(name +
                            "    waiting: " + taskStats.waiting +
                            "   complete: " + taskStats.complete);
                }

long rbegin = System.currentTimeMillis();
                //calc result..

long rend = System.currentTimeMillis();
                if (logLevel >= 5) {
                    String name = "thread" + Thread.currentThread().getName();
                    System.out.println(name +
                            "   waiting: " + taskStats.waiting +
                            "   complete: " + taskStats.complete);
                }
                long bbegin = System.currentTimeMillis();
                //requery result by employee

long bend = System.currentTimeMillis();
                taskStats.waiting--;

Thread.yield();
                taskStats.complete++;
                calcTime = (bend-bbegin);
                queryTime = (rend-rbegin);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

@Override
    public TaskStats call() throws Exception {
           runOnce(); // warmup
           for (int i = 0; i < nrIterations; i++) {
               runOnce();
               taskStats.totalcalcTime += calcTime;
               taskStats.totalqueryTime += queryTime;
               taskStats.nrSamples++;
               if (logLevel >= 2) {
                   String name = "thread" + Thread.currentThread().getName();
                   System.out.println("result " + taskStats.nrSamples + "\t"
                           + name + "\t" + "borrow time: " + calcTime + "\t"
                           + "return time: " + queryTime + "\t" + "waiting: "
                           + taskStats.waiting + "\t" + "complete: "
                           + taskStats.complete);
               }
           }
           return taskStats;
       }
    }

private void run(int nrIterations, int nrThreads) {
        this.nrIterations = nrIterations;

ExecutorService threadPool = Executors.newFixedThreadPool(nrThreads);

List<Callable<TaskStats>> tasks = new ArrayList<Callable<TaskStats>>();
        for (int i = 0; i < nrThreads; i++) {
            tasks.add(new PerfTask());
            Thread.yield();
        }

if (logLevel >= 1) {
            System.out.println("created");
        }
        Thread.yield();
        List<Future<TaskStats>> futures = null;
        try {
            futures = threadPool.invokeAll(tasks);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

if (logLevel >= 1) { System.out.println("started"); }
        Thread.yield();

if (logLevel >= 1) { System.out.println("go"); }
        Thread.yield();

if (logLevel >= 1) { System.out.println("finish"); }

TaskStats aggregate = new TaskStats();
        if (futures != null) {
            for (Future<TaskStats> future : futures) {
                TaskStats taskStats = null;
                try {
                    taskStats = future.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                if (taskStats != null) {
                    aggregate.complete += taskStats.complete;
                    aggregate.nrSamples += taskStats.nrSamples;
                    aggregate.totalcalcTime += taskStats.totalcalcTime;
                    aggregate.totalqueryTime += taskStats.totalqueryTime;
                    aggregate.waiting += taskStats.waiting;
                }
            }
        }

System.out.println("-----------------------------------------");
        System.out.println("nrIterations: " + nrIterations);
        System.out.println("nrThreads: " + nrThreads);
        System.out.println("nrSamples: " + aggregate.nrSamples);
        System.out.println("totalcalcTime: " + aggregate.totalcalcTime);
        System.out.println("totalqueryTime: " + aggregate.totalqueryTime);
        System.out.println("avg calcTime: " +
                aggregate.totalcalcTime/aggregate.nrSamples);
        System.out.println("avg queryTime: " +
                aggregate.totalqueryTime/aggregate.nrSamples);

threadPool.shutdown();
    }

@Test
    public void testDeadLocked() {
        setLogLevel(0);
        System.out.println("Increase threads");
        run(10,  20);
        run(10, 30);
        run(10, 40);
    }
}

为了尽可能的接近真实情况,这个代码最好在两台以上的电脑上运行,并且要注意避免sql共享机制,这点可以让参数随机产生。

程序跑了10分钟后,死锁果真出现了,如log错误描述一般。

一次查找sqlserver死锁的经历的更多相关文章

  1. 查找 SqlServer死锁

    use master if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_who_lock]') ) dr ...

  2. sqlserver 死锁原因及解决方法

    其实所有的死锁最深层的原因就是一个:资源竞争 表现一: 一个用户A 访问表A(锁住了表A),然后又访问表B,另一个用户B 访问表B(锁住了表B),然后企图访问表A,这时用户A由于用户B已经锁住表B,它 ...

  3. 模拟 SQLSERVER 死锁

    环境: sqlserver 2008   事务(进程 ID (n))与另一个进程被死锁在锁资源上,并且已被选作死锁牺牲品.请重新运行   死锁原理: 如两个任务 任务1,已经锁定R1,再进行请求R2& ...

  4. SqlServer死锁与阻塞检测脚本

    IF EXISTS (SELECT * FROM sysobjects WHERE [name] = 'sp_Lock_Scan') DROP PROCEDURE sp_Lock_Scan GO CR ...

  5. sqlserver 死锁查看辅助存储过程

    USE [master] GO /****** Object: StoredProcedure [dbo].[sp_who_lock] Script Date: 03/23/2016 14:17:49 ...

  6. SQLSERVER 死锁标志

    最开始做DBA的时候,整天死锁到头痛1222,至今都能回想到这个错误窗口: 死锁定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待 ...

  7. 验证SQLServer死锁进程

    SELECT '现在没有阻塞和死锁信息' AS message -- 循环开始WHILE @intCounter <= @intCountProperties BEGIN-- 取第一条记录 SE ...

  8. sqlserver 死锁相关

    参考 https://www.cnblogs.com/fuyuanming/p/5783421.html -- 查询死锁 select request_session_id spid, OBJECT_ ...

  9. 查找sqlserver数据库中,查询某值所表名和字段名

    有时候我们想通过一个值知道这个值来自数据库的哪个表以及哪个字段,通过一个存储过程实现的.只需要传入一个想要查找的值,即可查询出这个值所在的表和字段名. 前提是要将这个存储过程放在所查询的数据库. CR ...

随机推荐

  1. (转)Systemd 入门教程:命令篇

    Systemd 入门教程:命令篇 原文:http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html Systemd 入门 ...

  2. Oracle 锁机制

    本文参考自:ORACLE锁机制 1.oracle是一个多用户使用的共享资源,当多个用户并发的操作同一数据行时,那么在oracle数据库中就会存在多个事务操作统一数据行的操作,如果不对并发操作进行控制, ...

  3. intellij idea NoClassDefFoundError javax.swing.UIManager

    今天启动idea报 NoClassDefFoundError javax.swing.UIManager 可是明明配置好了java 环境 ,后来仔细想了一下只配置了java的bin目录在PATH里 随 ...

  4. unity2018的坑点

    发布后有的电脑无法运行exe程序(反正我的电脑不行) 删除发布出来的一个叫UnityCrashHandler64.exe即可运行

  5. MySQL使用内置函数来进行模糊搜索(locate()等)

    常用的一共有4个方法,如下: 1. 使用locate()方法 1.1.普通用法: SELECT `column` from `table` where locate('keyword', `condi ...

  6. ASP.NET能知道的东西

    ASP.NET能知道的东西 获取服务器电脑名: Page.Server.ManchineName 获取用户信息: Page.User 获取客户端电脑名:Page.Request.UserHostNam ...

  7. 在C++中实现类似Java的“synchronized”

    我只是代码的搬运工,原文参见A "synchronized" statement for C++ like in Java.其实现是通过区域锁(Scoped locking)和宏定 ...

  8. Java 基础(4)——常量 & 注释

    hello 呀,今天的内容超简单( ̄︶ ̄)↗并且,还有暗藏福利哟~~ 常量 常量 就是常常不变的量,第一次定义之后,就不会发生改变了.可能这就是 “常量” 的来源吧哈哈哈(玩笑). 一般来说,常量的定 ...

  9. 2018-12-25 课堂笔记&面试题

    面试题一.Java中,八大基本数据类型有哪些?答:数值型:整型(byte.short.int.long)浮点型(float.double)非数值型:布尔类型(boolean)字符型(char).注意: ...

  10. DIV固定宽度和动态拉伸混合水平排列

    1.效果图 2.源代码 html <h2>1.头部固定,尾部拉伸</h2> <div class="container" id="div1& ...