必须禁用 ON DELETE CASCADE,因其触发 InnoDB 内部级联删除,绕过事务控制,无索引时全表扫描并持 IX 锁导致阻塞;须同步建独立外键索引、应用层分步删子父表、删除外键约束。

必须禁用 ON DELETE CASCADE,这不是优化选项,而是锁风险的根源。 InnoDB 的级联删除由引擎内部触发,不走你的 SQL 路径,也不受事务语句顺序控制——它会扫描子表、加 X 锁,没索引就直接全表扫描 + 表级 IX 锁,多个事务一碰就卡死,连死锁都算不上,是阻塞。
为什么 ON DELETE CASCADE 会引发不可控加锁
你执行 DELETE FROM parent WHERE id = 123,InnoDB 不仅锁 parent 行,还会立即扫描所有 child_table.parent_id = 123 的行并加 X 锁。这个过程:
- 不经过你的应用层事务控制,无法
FOR UPDATE预占、也无法按需分批 - 如果
child_table.parent_id没独立索引,就会全表扫描,整张子表被意向锁(IX)堵死 - SHOW ENGINE INNODB STATUS 中会出现
lock_mode X locks index `fk_idx`,但你的代码里根本没写那条子表语句
三步不可省:建索引、拆级联、删外键
只做其中一两个,问题照旧。必须同步完成:
- 为每个外键列单独建索引:
ALTER TABLE child_table ADD INDEX idx_parent_id (parent_id);。联合索引前缀不认,InnoDB 只认独立单列索引作为“外键索引” - 把级联逻辑移到应用层:先
SELECT id FROM child_table WHERE parent_id = ? FOR UPDATE查出 ID 列表,再DELETE FROM child_table WHERE id IN (?,?,?),最后删父表。整个流程包裹在同一个事务中 - 删掉外键约束:
ALTER TABLE child_table DROP FOREIGN KEY fk_name;。残留外键仍会触发 S 锁校验,哪怕已禁用级联
应用层实现时最容易漏的关键点
显式两步看似简单,但漏掉任一点都会让死锁/阻塞复现:
-
SELECT ... FOR UPDATE必须加,且不能JOIN parent_table—— 否则会额外锁父表,扩大锁范围 - 整个操作必须在一个事务内完成:
BEGIN→ 子表查+删 → 父表删 →COMMIT - 所有业务路径严格按「先子后父」顺序执行。子表数据量通常更大,早释放更安全
最常被忽略的是:即使你已把级联搬到应用层,只要 parent_id 列没独立索引,SELECT ... WHERE parent_id = ? 就仍会全表扫描并持表级 IX 锁——这不是死锁日志能轻易暴露的问题,而是静默卡死。索引不是“锦上添花”,是前提条件。


















