MySQL并发插入导致死锁

大家好,我是「云舒编程」,今天我们来聊聊# MySQL并发插入导致死锁。

文章首发于微信公众号:云舒编程
关注公众号获取:
1、大厂项目分享
2、各种技术原理分享
3、部门内推

背景

某天下午组里有一个对外提供创建租户的接口突然产生了MySQL死锁的报警。
该服务是一个老服务,至少有一年没有人改动过该接口,并且租户这个场景只支持创建和查询,其他能力都不支持。收到报警的一刻,内心充满了疑惑:“这也能死锁?”

死锁日志

先是到MySQL上获取了死锁日志:
image.png
关于插入意向锁,MySQL官网有如下解释说明:
image.png
结合死锁日志和官网说明大概推断死锁原因是:

  • 事务一持有了某个记录的S型Next_LOCK锁(也就是S锁+GAP锁),然后等待另一个S型Next_LOCK锁。
  • 事务二持有了某个记录的X锁,同时想获取某个间隙的插入意向锁。

也就是形成了下图的环:
image.png
其实到这还是无法根据死锁日志信息看出来为什么会形成环。不过由于表中的tenant_id是由调用方指定传入的,所以可以根据tenant_id去搜索日志,找到对应的trace_id,追踪当时整个链路发生了什么。

链路分析

不搜不要紧,一搜吓一跳。根据tenant_id搜索发现从网关发起了两条一模一样的请求,发起的时间也是一模一样。也就是说在MySQL层产生了并发插入。
image.png
同时发现插入数据的代码居然是使用的for循环插入,而不是批量插入。

for _, tenant := range tenants {
		if err := model.GetDB().Model(&model.Goods{}).Create(tenant).Error; err != nil {
			return nil, err
		}
	}

同时在MySQL官网找到一段关于并发插入可能导致死锁的说明:
image.png
按照图中的说法,当插入一条数据时会先给该数据加上排他锁,如果发生了「duplicate-key error」,那么就会加上共享锁,这样就会导致当出现多个会话同时插入数据并且发生「duplicate-key error」时就会导致死锁。
同时文中给了一个死锁案例:
image.png
刚好我们的t_tenant表中的tenant_id是唯一索引。不过官网的案例跟我获取的死锁信息和请求链路信息有所不同,

  • 官网的死锁必须要3个或者3个以上的并发才会导致死锁,但是我的并发只有两个,按照图中的举例产生不了死锁的条件。
  • 官网举例中只会产生排他锁和共享锁,但是死锁日志中出现了S型Next_lock锁。

不过由于没有新的突破点,打算先按照并发插入导致死锁的思路尝试进行复现,如果可以稳定复现,那应该就是并发加上唯一索引重复冲突导致的了。
也就是说当时的场景应该如下:

<
时刻 事务一 事务二
### 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、付费专栏及课程。

余额充值