MySQL插入时死锁

死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,这些事务都将无法向前推进,这里主要介绍插入时发生死锁的一些情况

模拟插入时发生死锁

新建一张表

表结构

CREATE TABLE `test_test` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `version` smallint DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

1. 插入两条相同的数据

t1t2状态
begin;begin;
insert ignore test_test (id, version) values (5, 1);成功
insert ignore test_test (id, version) values (5, 1);锁等待状态

insert加的是隐式锁,隐式锁可以理解为没有锁
在t1插入记录时是不加锁的,这个时候事务t1还未提交的情况下,事务t2尝试插入的时候,发现有这条记录,t2尝试获取S锁,会判定记录上的事务id是否活跃,如果活跃的话,说明事务未结束,会帮t1把它的隐式锁提升为显式锁(X锁)

2. 批量插入顺序不一致的导致的死锁

t1t2状态
beginbegin
insert ignore test_test (id, version) values (5, 1);成功
insert ignore test_test (id, version) values (6, 2);成功
insert ignore test_test (id, version) values (6, 2);t1尝试获取S锁,把t2的隐式锁提升为显示X锁,进入DB_LOCK_WAIT
insert ignore test_test (id, version) values (5, 1);t2尝试获取S锁,把t1的隐式锁升级为显示X锁,产生死锁

3. 三个 insert 一个回滚造成的死锁

t1t2t3说明
beginbeginbegin
insert ignore test_test (id, version) values (5, 1);成功
insert ignore test_test (id, version) values (5, 1);把t1的隐私锁升级为X锁,t2进去S锁等待
insert ignore test_test (id, version) values (5, 1);t3进入S锁等待
rollbackt1回滚后,释放X锁,t2、t3同时拿到了S锁
okdeadlockt2和t3都想拿到插入意向X锁,造成死锁条件

一个已提交但是未purge掉的记录会造成后续插入获取S共享锁,两个事务同时获取S锁,然后尝试获取插入意向锁,造成死锁

首先,为待插入的间隙加插入意向锁(Insert Intention Locks)。

  • 如果该间隙已存在 GAP 锁或 Next-Key 锁,则加锁失败并进入等待状态。
  • 否则,加锁成功,表示此间隙允许插入。

接下来,判断插入的记录是否具有唯一键。

  • 若没有唯一键,直接进行插入操作。

  • 若存在唯一键,进行唯一性约束检查。

    • 若不存在相同的键值,完成插入操作。

    • 若存在相同的键值:

      1. 判断该键值是否已被锁定。

        • 若未被锁定:

          1. 判断该记录是否被标记为删除。

            • 若未标记删除,报错 1062(duplicate key)。

            • 若标记为删除,事务可能正在进行但尚未提交,因此加 S 锁并等待。

        • 若已被锁定:

          1. 表示该记录正在处理(插入、删除或更新),且事务未提交,加 S 锁并等待。

对插入的记录加 X 记录锁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值