mysql批量insert死锁

本文分析了多线程批量插入订单数据时出现的MySQL死锁问题。通过解析日志,揭示了两个事务在不同记录上形成交替等待的死锁场景,并提出了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景:

清结算流程,用户为维度多线程进行订单、金额的清结算,在批量insert资产历史(每个用户每个币种只有一条数据)时mysql deadlock。

dump日志:

Transactions deadlock detected, dumping detailed information.
2022-09-27T18:11:13.600331+08:00 72980645 [Note] InnoDB:
*** TRANSACTION:



TRANSACTION 2864287581, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
21 lock struct(s), heap size 1136, 26 row lock(s), undo log entries 19
MySQL thread id 72980645, OS thread handle 140596170843904, query id 13810651742 192.168.8.31 root update
INSERT INTO td_fund_asset_history (id, record_no, record_date, record_time, user_id, account_id, total_market_value, assets_type, total_income, holding_income, yesterday_income, remark )
values
  
(null, '1574371280161431552', '2022-09-27 00:00:00.0', '2022-09-27 18:11:13.598', '200048459', '200048459', 120001.1800, 'USD', 1.18000, 1.18000, 1.18000, '首次保存' )
,
(null, '1574688535153467392', '2022-09-27 00:00:00.0', '2022-09-27 18:11:13.598', '200048459', '200048459', 29999.9900, 'HKD', 0.00000, 0.00000, 0.00000, '首次保存' )
2022-09-27T18:11:13.600371+08:00 72980645 [Note] InnoDB: *** HOLDS THE LOCK:



RECORD LOCKS space id 5026 page no 5087 n bits 688 index uk_record_no of table `za_invest_trade_00`.`td_fund_asset_history` trx id 2864287581 lock_mode X locks rec but not gap
Record lock, heap no 456 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95da6ae131b3e000; asc   j 1   ;;
2: len 8; hex 80000000000857cd; asc       W ;;



Record lock, heap no 466 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95d94a5682996000; asc   JV  ` ;;
2: len 8; hex 80000000000857cc; asc       W ;;



***  WAITING FOR THIS LOCK TO BE GRANTED, WHICH CONFLICTS WITH THE LOCK HELD BY TRANSACTION 2864287583:
RECORD LOCKS space id 5026 page no 5087 n bits 688 index uk_record_no of table `za_invest_trade_00`.`td_fund_asset_history` trx id 2864287581 lock mode S waiting
Record lock, heap no 470 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95d94a5688196000; asc   JV  ` ;;
2: len 8; hex 80000000000857ce; asc       W ;;



2022-09-27T18:11:13.600877+08:00 72980645 [Note] InnoDB:
*** TRANSACTION:



TRANSACTION 2864287583, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 21 lock struct(s), heap size 1136, 23 row lock(s), undo log entries 17
MySQL thread id 72980643, OS thread handle 140596055435008, query id 13810651731 192.168.8.31 root update
INSERT INTO td_fund_asset_history (id, record_no, record_date, record_time, user_id, account_id, total_market_value, assets_type, total_income, holding_income, yesterday_income, remark )
values
  
(null, '1574371280253706240', '2022-09-27 00:00:00.0', '2022-09-27 18:11:13.596', '200048460', '200048460', 60000.3900, 'USD', 0.39000, 0.39000, 0.39000, '首次保存' )
,
(null, '1574688535166050304', '2022-09-27 00:00:00.0', '2022-09-27 18:11:13.596', '200048460', '200048460', 59999.9800, 'HKD', 0.00000, 0.00000, 0.00000, '首次保存' )
2022-09-27T18:11:13.600912+08:00 72980645 [Note] InnoDB: *** HOLDS THE LOCK:



RECORD LOCKS space id 5026 page no 5087 n bits 688 index uk_record_no of table `za_invest_trade_00`.`td_fund_asset_history` trx id 2864287583 lock_mode X locks rec but not gap
Record lock, heap no 470 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95d94a5688196000; asc   JV  ` ;;
2: len 8; hex 80000000000857ce; asc       W ;;



***  WAITING FOR THIS LOCK TO BE GRANTED, WHICH CONFLICTS WITH THE LOCK HELD BY TRANSACTION 2864287581:
RECORD LOCKS space id 5026 page no 5087 n bits 688 index uk_record_no of table `za_invest_trade_00`.`td_fund_asset_history` trx id 2864287583 lock mode S waiting
Record lock, heap no 456 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95da6ae131b3e000; asc   j 1   ;;
2: len 8; hex 80000000000857cd; asc       W ;;



*** WE ROLL BACK TRANSACTION 2864287583

日志分析:

MySQL thread id 72980645:线程id,后面跟随此次线程执行的sql;

[Note] InnoDB: *** HOLDS THE LOCK:HOLDS代表该线程持有以下锁;

RECORD LOCKS:持有的是记录锁/行锁,记录锁永远都是加在一行数据上的;

lock_mode X locks rec but not gap:锁类型为X锁,但并不是间隙锁;

n_fields 3:表示锁到的索引,有关系的字段为三个,以下就是被锁的数据行的这个索引相关的字段值;

WAITING FOR THIS LOCK:等待的锁信息。

简化、抓取关键日志:

事务  2864287581
uk_record_no:record_date、record_no、id
Record lock, heap no 456 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95da6ae131b3e000; asc   j 1   ;;
2: len 8; hex 80000000000857cd; asc       W ;;


wait lock
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95d94a5688196000; asc   JV  ` ;;
2: len 8; hex 80000000000857ce; asc       W ;;


事务  2864287583
Record lock, heap no 470 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95d94a5688196000; asc   JV  ` ;;
2: len 8; hex 80000000000857ce; asc       W ;;


wait lock
0: len 3; hex 8fcd3b; asc   ;;;
1: len 8; hex 95da6ae131b3e000; asc   j 1   ;;
2: len 8; hex 80000000000857cd; asc       W ;;

 从以上日志可以看出,

事务2864287581获取了80000000000857cd这行记录的锁,等待80000000000857ce这行记录的锁;

事务2864287583获取了80000000000857ce这行记录的锁,等待80000000000857cd这行记录的锁;

形成了交替等待的场景,形成了死锁。

但是,为什么多线程批量insert时,会出现这种情况呢?

后续:

针对单字段的唯一索引,或主键,是不会加gap锁的,原因是不会有两条及以上同UK的数据;

联合键的唯一索引,是无法确定数据的唯一性的,所以会加gap锁,除非明确了UK键中的所有字段值;

针对批量insert时,会加X锁并且两边加上开区间的间隙锁,这也是为了防止幻读;

所以当事务1的数据分别在[1,5]、[6, 7]区间插入,事务2在[6, 7]、[1, 5]的区间插入,事务1成功在[1,5]插入,事务2成功在[6, 7]插入,这时就会发生事务1等[6, 7]区间的锁释放,事务2等[1, 5]区间的锁释放,就会导致这个问题了。

解决方案:批量插入改为单条插入,就不会发生锁互相等待的情况了

### MySQL 批量插入过程中的死锁现象及预防 #### 死锁定义与成因 当多个事务试图以循环方式锁定相同的资源时,就会发生死锁。具体来说,在MySQL中,如果两个或更多事务相互等待对方释放锁,而这些事务又都无法继续执行下去,就形成了死锁情况[^1]。 对于批量插入操作而言,常见的死锁原因包括但不限于: - 插入数据涉及到相同记录上的排他锁请求; - 不同会话按照不同顺序获取同一组行级锁; - 使用`index_merge`优化器策略导致不必要的额外加锁行为; #### 查询当前系统的锁争用状况 为了更好地理解系统内部发生的锁竞争情形,可以通过以下SQL语句来查看InnoDB存储引擎级别的行级别锁统计信息: ```sql SHOW STATUS LIKE 'innodb_row_lock%'; ``` 该命令返回的结果可以帮助诊断是否存在潜在的高频率锁冲突问题[^2]。 #### 解决策略 针对上述提到的各种可能引发死锁的因素,采取相应的措施能够有效降低甚至消除死锁的发生几率: ##### 合理设计表结构并建立适当索引 确保数据库模式合理高效,特别是要考虑到并发写入场景下的性能需求。例如,创建合适的联合索引可以让查询更精确地定位目标数据集,从而减少不必要的锁范围扩大化倾向。这不仅有助于提高吞吐量,也能间接缓解由不当索引引起的死锁风险[^3]。 ##### 控制事务隔离级别 调整默认读已提交(Read Committed)或者可重复读(Repeatable Read)之外更低强度的一致性保障机制——脏读(Dirty Reads),虽然这样做可能会引入其他类型的不一致性问题,但在某些特定应用场景下确实可以显著改善多版本控制开销过高的局面,进而减轻死锁压力。 ##### 调整应用逻辑实现重试机制 应用程序层面应该具备处理异常的能力,即一旦检测到发生了死锁错误(通常是通过捕获特定编号如1213),则立即回滚失败交易,并稍作延时之后再次尝试发起同样的更新动作直到成功为止。这种做法既简单又能很好地应对偶发性的短暂阻塞事件。 ##### 优化批处理流程 尽可能将大批量的数据拆分成较小批次逐步加载入库,这样做的好处是可以让每次DML操作影响的对象数量更加可控,同时也减少了长时间持有共享/独占锁的可能性。 #### 实际案例分析 假设有一个电商网站后台管理系统正在经历频繁的商品库存同步任务期间遭遇大量死锁报警提示。经过排查发现是因为商品分类维度较多且存在交叉关联关系所致。于是决定重构原有业务模型,新增一张专门用于维护实时库存快照视图的小型辅助表格,并为之配置复合唯一键作为主键字段组合之一。这样一来既满足了快速检索的要求也规避掉了原生大宽表带来的复杂依赖链路所造成的各种麻烦事端。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值