RC隔离级别下仍会产生死锁,根本原因是行锁加锁顺序不一致与多索引路径交叉引发循环等待;虽无间隙锁,但X锁、S锁及唯一索引冲突检测中的临时锁仍可形成S-X互斥闭环。

RC隔离级别下仍会产生死锁,根本原因不是“有没有间隙锁”,而是行锁加锁顺序不一致 + 多索引路径交叉触发循环等待。
RC下为什么还有X锁和S锁参与死锁
Read Committed(RC)确实不使用间隙锁(Gap Lock),但所有写操作(UPDATE、DELETE)仍必须加排他锁(X锁),而显式加锁的读操作(如 SELECT ... LOCK IN SHARE MODE)仍会加共享锁(S锁)。这些锁之间存在明确的兼容性冲突:
-
X与X不兼容 → 两个事务同时更新同一行会阻塞,但不直接构成死锁 -
S与X不兼容 → 一个事务持S锁读某行,另一个事务想UPDATE它,就会等待 - 关键点:当多个事务对**不同索引路径上的同一逻辑记录**按不同顺序加锁时,
S和X就可能形成环路
死锁常发生在唯一索引+主键回表场景
比如表有主键 inode_id 和唯一索引 key_pid_name(parent_id, name),事务执行:
SELECT * FROM table_ofs WHERE parent_id = 224 AND name = '00004_0' LOCK IN SHARE MODE;
这条语句会先在 key_pid_name 索引上加 S 锁,再根据该索引查到的 inode_id 回主键索引加 S 锁(或隐式获取);而另一事务执行:
INSERT INTO table_ofs (inode_id, name, parent_id) VALUES (704, '00004_0', 224);
这条语句需在 key_pid_name 索引上检查唯一性,会先加插入意向锁(INSERT INTENTION),再尝试加 X 锁 —— 此时若前一事务已持该索引上某 gap 或记录的 S 锁,就可能卡住。
- RC 下虽无 gap lock,但唯一索引的 **duplicate check 过程会临时持有 S 锁或 X 锁**
- 如果事务1先查
parent_id=893,再查parent_id=224;事务2反向操作,就极易形成事务1(S on 893) → 等待 X on 224,事务2(S on 224) → 等待 X on 893的闭环 - MySQL 8.0+ 中,即使
SELECT ... LOCK IN SHARE MODE在 RC 下也**不会立即释放 S 锁**,而是等到事务结束才释放
存储过程里更容易踩坑的三个点
存储过程封装了多步 SQL,事务边界不易察觉,容易放大死锁风险:
- 隐式事务未显式控制:
AUTOCOMMIT=1时每条语句自动提交,但一旦用BEGIN开启事务,后续所有语句都在同一事务内 —— 锁持续到COMMIT或ROLLBACK - 动态拼接条件导致执行计划漂移:同一条存储过程,在不同参数下可能走不同索引,加锁范围和顺序完全不可控
- 未统一资源访问顺序:比如先查 A 目录再查 B 目录,但并发调用时参数乱序,底层索引扫描顺序随之变化,锁顺序就乱了
真正难处理的不是“RC该不该有死锁”,而是**索引结构 + 执行路径 + 应用逻辑三者耦合后,锁行为变得难以预测**。排查时别只盯着隔离级别,优先看 SHOW ENGINE INNODB STATUS\G 输出里的锁类型、索引名、SQL 实际走的 key,再反推加锁链条是否可能成环。

















