delete join 更容易锁表,因其执行需多表扫描与关联匹配,若join字段无索引则触发全表扫描并加临键锁,锁范围取决于扫描行数而非删除行数,且语法强制嵌套循环加剧锁竞争。

DELETE JOIN 为什么比单表 DELETE 更容易锁表
因为 DELETE JOIN 的执行路径天然涉及多表扫描和关联匹配,InnoDB 必须对驱动表和被驱动表都加锁——而一旦某张表的 JOIN 字段没索引,就会触发全表扫描,进而对所有扫描到的行、间隙甚至索引页加临键锁(next-key lock)。这不是“可能锁表”,而是“大概率逻辑锁表”。
- 哪怕只删 1 行,只要
ON t1.id = t2.user_id中t2.user_id没索引,t2就会被全表扫描,RR 隔离级别下会锁住整个主键索引的所有间隙 -
EXPLAIN显示type: ALL或key: NULL,基本可断定该表在 JOIN 过程中无索引可用 - 锁范围不取决于你“想删几条”,而取决于优化器“要扫多少行”——这和
SELECT JOIN是同一套执行计划
MySQL 中 DELETE JOIN 的语法陷阱直接放大锁风险
MySQL 要求必须写成 DELETE t1 FROM t1 JOIN t2 ON ...,这个语法本身就会让优化器更倾向于走嵌套循环(NLJ),尤其当 t2 没索引时,外层每扫 t1 一行,内层就要全扫一遍 t2。结果就是锁被反复申请、持有时间拉长,极易阻塞其他事务。
- 别名不一致(比如
DELETE u FROM users u写成DELETE u FROM users u2)会报错,但错误提示不提锁的事,容易让人误以为只是语法问题 - 漏写
WHERE条件?DELETE t1 FROM t1 JOIN t2会删掉所有匹配行,且无法用SELECT预览——线上执行等于盲删 - 即使加了
WHERE,如果条件字段(如t2.status = 'cancelled')没索引,照样触发全表扫描 + 全量加锁
不同数据库对 DELETE JOIN 的支持差异让问题更隐蔽
MySQL 支持 DELETE JOIN,但 SQL Server 和 Oracle 完全不认这种写法。开发在 MySQL 上验证通过的语句,一放到其他环境就报错,逼着你临时改用子查询——而子查询若没控制好结果集大小,同样会锁表(比如 IN 子查询返回几十万 ID)。
- SQL Server 报
Msg 156, Incorrect syntax near the keyword 'INNER',本质是语法不兼容,不是逻辑错 - Oracle 报
ORA-00933: SQL command not properly ended,必须改成DELETE FROM t1 WHERE EXISTS (...) -
EXISTS比IN更安全:前者不会因子查询返回NULL失效,且优化器更容易用上索引
真正可控的删除方案:分批 + 索引 + 执行计划验证
不要指望一条 DELETE JOIN 干完所有事。线上删几万行以上,必须拆成每次几百或几千行,并确认每批都走索引。否则锁持有时间超过 1 秒,业务写入就开始排队。
- 先跑等价
SELECT COUNT(*),再用EXPLAIN FORMAT=JSON看used_columns和key字段,确保每张 JOIN 表都命中索引 - 删前加休眠:
DELETE t1 FROM t1 JOIN t2 ON ... LIMIT 1000; SELECT SLEEP(0.1);—— 别省这 100ms,它能大幅降低锁冲突概率 - 如果
t2是大表且常用于 JOIN 删除,优先给t2.user_id加索引;联合索引要按WHERE条件排序,比如(user_id, status)比单列user_id更高效
LEFT JOIN,只要 WHERE t2.status IS NOT NULL,优化器就可能把它重写成 INNER JOIN,锁范围瞬间翻倍。查 EXPLAIN 不是可选项,是上线前必做动作。











