• 【线上问题排查】数据库死锁


    前言:

      MySql数据库事务特性AICD,原子性、隔离性、一致性和持久性,本文要介绍的就是一个因为隔离性而导致的问题,该问题不具有普适性,因为带有了一小部分异常被覆盖的前置条件,该条件导致了问题更难发现

    背景:

      生产上有两个系统,一个接单系统,一个作业系统,作业系统负责接单系统下发的单子的执行任务,作业系统在某道工序,会产生一个任务插入数据库,该任务的执行由接单系统每分钟捞数据库执行,任务的生成和执行是分离的,所以存在一分钟延时,本次需求就是为了延时减到最低(最好立即发送),所以需要作业系统同步去调用接单系统发送任务。

    现象:

      改造完成后,发现该同步调用,总是RPC超时,而去接单系统查看,超时的原因是每到6秒就会抛异常,有了异常信息应该很好定位,然而异常却看不出任何信息,下面是该异常:

    java.lang.reflect.UndeclaredThrowableException

      被吃掉的异常 该异常产生的原因和JDK的动态代理、检查异常有关,简而言之,就是没有捕捉检查异常导致异常被包装成为 UndeclaredThrowableException 抛出,从而无法得知原始异常的信息。

      因为该次任务有数据库操作,而且通过arths排查发现在数据库每次都跑6s多,刚开始以为索引问题,也以为是切了数据库的问题。但都被排除了

    疑点:

      问题的排查需要静下来思考的,而且要结合多个方面,如果思考实在没问题就要去了解框架机制和原理,这些都要求对机制比较熟悉,这次其实就是通过一些观察,最终发现了问题。

      超时时间 6S :

      多试了几次任务调用,发现超时时间都是6S,这个时间比较诡异,一看就知道是某种超时机制触发的,所以首先肯定,是数据库的超时机制。

      Select for Update:

      我们排查到了,超时发生在某个查询方法

    public class OrderDao{
    
        public Order queryById(String Id,Boolean lock){
        
            ////查询,加锁
    
        }
    }    

      该方法的查询操作有个参数,lock=true,查看实现是在查询数据的时候,用到了select for update的特性,该特性就是为查找的数据库加上行锁;

      这个时候已经感觉到了一些端倪,这一定是数据库死锁了。

    原子性:

      同数据库的事务:

      为了验证我的猜想,去看了作业系统生成任务的代码,该代码在一个事务(事务A)中,而且该事务操作了Order的表;执行任务的接单系统,也是在一个事务中(事务B),该事务也需要查询Order的表,而且该这行数据加了锁,一下子,真相便出来了。

      事务时具有原子性的,为了保证这个原子性,数据库利用了 锁 这个计算机概念,保证加了锁的数据行不会被其他事务更改,所以,为了防止意外发生,下发任务和更改单据状态时在一个事物A中,保证了原子性,所以就给当前操作的order加了锁;同样,在事务A中同步调用作业系统产生的事物B为了原子性,为了for update操作必然也需要占有该行锁。因为锁已经被A获取,所以B只能等待,而A又等待B完成,所以就互相等待,导致死锁,超时抛异常;

      一个方案:

      那么更改一下这个查询,把 lock 参数该为 false,发现可行因为后面已经没有更新的操作了,所以当时这个select就是为了当前读,当前读?为什么要这些写呢?是不是原来的作者写错了呢?继续看下去

      意外:

      更改后,发现虽然不死锁了,但是查询的数据确实过时的数据,问题转移成为了另外一个问题,为什么是过时的数据呢?

    隔离性:

      不可行的方案:

      其实当时一下子就想到了,我们数据库设置的隔离级别是:可重复度,因为同步调用,事务A还没结束,事务具有隔离性,所以事物B如果不是 select for update,根本不可能读到下发任务前在事务A中更改的数据。所以同步调用的方案是不可行的。只能异步,而且事务B也必须要是快照读,因为假设异步,事务A下发任务后在没有结束的时候事务B执行了,读到的数据还是旧的。

      最终方案:

      把事物A下发事物B的方案改成了由下发任务,换成rocketMQ通知,这样既能保证任务的快速执行,也能保证数据的正确性。

    -------------------------------- 优秀、是一种习惯 、、、、、、、、、、、、、、、
  • 相关阅读:
    js练习-两个栈实现队列
    js练习- 给你一个对象,求有几层
    React Context上下文
    react-native StatusBar透明
    react-native-splash-screen 隐藏statusbar
    掘金转载-手写一个Promise
    multipart/form-data
    (转)浅谈测试驱动开发(TDD)
    Objective-C urlEncode urlDecode
    (转)在Xcode 7上直接使用Clang Address Sanitizer
  • 原文地址:https://www.cnblogs.com/iCanhua/p/15781227.html
Copyright © 2020-2023  润新知