• 多线程事务死锁问题分析总结(实战应用)


    实战回顾

    2018年11月28日 有两个客户在两个渠道购买了同一产品每人各买2笔
    系统应在29日做成交处理, 成交结束后, 更新一张记录表, 记录表根据产品代码和渠道代码作为Unique.
    成交使用已客户为维度的多线程成交.
    // 方法名为虚拟捏造, 并非实际使用方法名
    成交方法 chengjiao() 为独立事务;
    chengjiao() 方法内使用多线程的嵌套事务 NESTED doChengjiao()
    伪代码

    // 独立事务
    chengjiao() {
    	// 根据客户查出交易
    	List<Trade> lists = getTradeList();
    
    	new Thread ... (list);
    }
    
    // 嵌套事务
    doChengjiao();
    

    假如数据为 渠道 001 产品 002 渠道 002 产品 002
    那更新的记录两条线程都要取更新表里面更新 001&002记录 和 002&002;
    但是问题出在线程的执行顺序;
    两个客户每个人在不同渠道买了一笔, 一共四笔交易记录;
    线程A先去更新了 001 & 002 这条记录
    线程B先去更新了 002 & 002 这条记录
    之后
    线程B又去更新 001 & 002 这条记录; (问题在这已经出现)
    线程A去更新 002 & 002 这条记录;

    后续的线程B在更新的时候, 在等待这条记录之前的UPDATE事务提交或回滚, 而在占用这条记录的线程A想要提交需要等待002 & 002 这条记录提交或回滚, 而002 & 002这条记录正好被B线程占用, 由此造成了互相等待, 将更新表锁住.后续交易无法进行.后经人为干预(数小时后发现), 杀掉其中一条会话, 导致该会话数据回滚, 而另一个会话因为数据库等待时间过长, 数据也没有进行提交, 最后导致4笔交易全部回滚. 如果问题出在这也就没什么. 问题是每天这几笔交易都恰巧同时执行.就一直卡死. 最后在12月3号, 4笔交易成交了. 4个工作日.问题影响… 客户是拒绝的… 不过好在客户大度, 没有计较. … …

    至此将问题从生产日志取下, 分析, 复现, 解决,重新上线 共计 2周+, 期间对spring事务感悟颇深. 遂总结此文章. 整理, 学习.


    Sring 事务管理

    首先来看事务的四个特性:

    1. 原子性

      事务的执行将事务内所做的操作看做一个整体, 要么全部执行, 要么全部不执行.

    2. 隔离性 (可能导致死锁)

      简单来说, 两个事务在同时进行更新时,一个事务在更新, 另一个事务需要操作时,不可能看到这条记录之前的值, 需要等到之前的事务要么执行(事务提交),要么不执行(事务回滚). 才可以继续对该记录进行操作. 这也是事务的其中一个隔离级别, 也是默认最优隔离级别 READCOMMITED 读已提交;

    3. 一致性

      对于同处在一个事务中的数据而言. 需要保持所有的相关数据保持一致状态, 当事务执行完以后也要保持相关全部数据的正确性

    4. 持久性

      简单来说, 事物的提交之后的数据保存到数据库中, 进行持久化处理;

    事务的4个隔离级别

    隔离级别 脏读 不可重读 幻读
    读操作未提交 可能 可能 可能
    读操作已提交 不可能 可能 可能
    可重读 不可能 不可能 可能
    串行化 不可能 不可能 不可能

    事务的7个传播机制

    1. REQUIRED: 如果存在一个事务,支持当前事务。如果没有事务则开启
    2. SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
    3. NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务(不使用事务)
    4. NESTED: 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, REQUIRED 属性执行
    5. REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起(自己一个事务,独立事务)
    6. NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常(必须由非事务的方法调用)
    7. MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常(必须由带有事务的方法来调用)

    画重点

    spring的事务管理中, 让我们容易出现问题的几个传播约定

    1. REQUIRED
    2. NESTED

    1是默认传播机制, 2是嵌套传播机制;
    REQUIRED 如果你没有, 我就自己管自己, 如果有, 就用你的;
    NESTED 如果有, 我就听你的, 如果没有, 我就按照默认的走;

    举例说明:
    fun1() 方法1 是一个带事务的方法, 我们将使用fun1()来调用, fun2(), 此时的方法2 fun2()我们在配置事务的时候

    1. 配置了一个 REQUIRED , 那么此时的fun2()支持fun1()的事务, 与fun1() 事务相同, 你是什么事务, 我就是什么事务.
    2. 配置了一个NESTED, 那么这个时候的fun2() 则是存在fun1()的事务之中, 而不是另起一个事务的存在. 他的提交与回滚, 与 fun1() 共存, fun1() 提交, 我就提交, fun1()回滚, 我就回滚;

    当fun1() 方法1 不是一个带事务的方法 , 此时 REQUIRED 与 NESTED 意义相同; 都将自身新启事务. 独立提交或回滚;

  • 相关阅读:
    Beta冲刺 (6/7)
    Beta冲刺(5/7)
    Beta 冲刺 (4/7)
    Beta 冲刺 (3/7)
    软件产品案例分析(团队)
    Beta 冲刺 (2/7)
    Beta 冲刺 (1/7)
    java 常用设计模式及Spring中应用了哪些设计模式
    java 八大排序算法
    记录java学习计划及相关工作中用到的技术/工具
  • 原文地址:https://www.cnblogs.com/lvgo/p/13275819.html
Copyright © 2020-2023  润新知