生产问题总结(1)->死锁问题

1 死锁概念

1.1 死锁的标志

死锁问题是生产上比较常见的问题, 异常信息是

com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException:Deadlockfoundwhentryingtogetlock;tryrestartingtransaction

1.2 死锁的必要条件

  1. 多个并发事务(2个或者以上);
  2. 每个事务都持有锁(或者是已经在等待锁);
  3. 每个事务都需要再继续持有锁(为了完成事务逻辑,还必须更新更多的行);
  4. 事务之间产生加锁的循环等待,形成死锁。

1.3 加锁条件

1 更新条件为主键或者有索引->加行锁

2 更新条件无索引-> 加表锁

1.4 如何避免发生死锁

1 大事务化为小事务:

大事务执行时间长,锁的占用时间就长, 其他线程等待锁时间也越长, 发生死锁的概率也就越高

2 更新条件加索引:

如果不加索引, 锁的是整张表 , 针对这张表的任何更新操作都要等待锁释放,所以不要因为表数据量小就不加索引, 针对于频繁更新的字段更要加索引, 降低锁的粒度

3 按照顺序更新 :

行锁产生的死锁是生产最常见原因, 核心问题就是两边线程更新顺序不一致,

针对于同一张表不同行的死锁可以按照 id 顺序更新

针对于不同表的死锁可以在代码中约定更新表的顺序

1.5 死锁检测

wait-for graph原理

MySQL 每个事务执行都有对应的事务 id,记录这持有锁,等待锁信息, 当发生锁等待时就会触发wait-for graph算法

比如,事务1给A加锁,事务2给B加锁,同时事务1给B加锁(等待),事务2给A加锁就发生了死锁。发生死锁, 发生死锁后 MySQL会默认回滚最小的事务(更新行数最少)

案例 1 : erp 同步库存与定时同步库存的死锁

1.1 问题发现

11-16晚, 在 pps 定时任务刷新库存缓存入库时,发生了数据库死锁

生产问题总结(1)->死锁问题_第1张图片

1.2 找到问题核心方法和参数

继续剖析得到产生死锁的方法和参数

com.weidiango.pps.stock.impl.ProductSkuStockServiceImpl#updateSkuSellStockCore

生产问题总结(1)->死锁问题_第2张图片

 

定位出 更新 t_product 表 id=674987 发生了死锁

1.3 调用端排查

该方法为更新库存通用方法,调用端如下

* 调用方:
* 1 erp推送修改库存
* 2 下单缓冲区定时入库
* 3 订单发货前修改sku/售后退货/换货扣减sku库存
* 4 下单库存为0->非0 或者 非0->0 时
* 5 平台产品列表手动更新
* 6 添加预售/现货补货计划增加sku库存
* 7 商家后台手动更新

具体哪两个调用端并发调用导致了死锁呢? 继续查看 loghub 日志排查

生产问题总结(1)->死锁问题_第3张图片

 

死锁一般都是并发操作导致的, 同时更新 t_product 表 id=674987 在该秒内有两处

1 更新 erp 推送的库存 (正常执行,未回滚)

2 定时任务执行更新缓冲区库存入库 (死锁, 抛出异常回滚)

1.4 rds 死锁诊断分析

生产问题总结(1)->死锁问题_第4张图片

通过步骤 3 推测出两个调用端分别为

1 更新 erp 推送的库存 (事务 2)

2 定时任务执行更新缓冲区库存入库 ( 事务 1)

这里主要看下事务 1,2 的持有锁和等待锁

持有锁: 证明当前事务更新过改行, 但是事务还未提交,锁还处于持有状态

等待锁 : 其他事务中持有锁,其他事务暂未提交, 当前事务获取不到锁, 处于等待状态

事务 1 的等待锁 : t_product , 证明在其他事务中有对 t_product表的修改,还未提交

事务 2 的等待锁 : t_item , 说明其他事务中有对 t_item 表的修改,还未提交

事务 2 的持有锁 : t_product, 说明在事务 2 中更新过 t_product 表, 还未进行提交

1.5 分析代码执行流程

生产问题总结(1)->死锁问题_第5张图片

1.6 问题解决方案

com.weidiango.pps.stock.impl.ProductSkuStockServiceImpl#updateSkuSellStockCore

该接口为更新 sku 库存接口, 在此接口中会判断库存是否存在缓冲区,存在缓冲区会

更新 t_item表,与更新 t_product 表 两处更新存在间隔, 其他线程真对同一 productId先更新 t_product 后更新 t_item 时 会产生死锁

经过分析: 该方法更新库存时更新缓冲区销量本身逻辑存在不合理的情况, 产生了一定的耦合性, 所以吧更新t_item 操作统一放在定时任务批量操作,不再耦合在更新库存方法中, 这样就解决了 两张表不按照顺序更新时产生的死锁

案例 2 : 并发操作换货时死锁

2.1 问题发现

当两个线程并发操作换货操作, a->b, b->a 时, 会发生死锁

2.2 分析代码执行流程

生产问题总结(1)->死锁问题_第6张图片

2.3 问题解决

互换死锁是未按照固定的顺序更新导致的, 解决方案是将 a,b sku 根据 id 排序, 按照固定的顺序更新库存

参考链接

Mysql 死锁和死锁的解决方案_三3三的博客-CSDN博客_mysql 表死锁

mysql死锁问题分析 - chengchao - 博客园

你可能感兴趣的:(生产事故总结篇,java,数据库,mysql)