首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >多线程事务死锁问题分析总结(实战应用)

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

作者头像
星尘的一个朋友
发布于 2020-11-25 15:08:33
发布于 2020-11-25 15:08:33
1.4K0
举报

实战回顾

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

代码语言:javascript
复制
// 独立事务
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 意义相同; 都将自身新启事务. 独立提交或回滚;

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/12/26 ,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 实战回顾
  • Sring 事务管理
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档