delete超时主因常是锁等待而非检索慢;即使有主键,next-key lock可能锁住大范围,需查innodb_trx与innodb_lock_waits定位阻塞事务。

DELETE 带主键却超时,大概率不是检索慢,而是锁等待
主键查询本身极快,SELECT * FROM orders WHERE id = 123 走聚簇索引查找通常在毫秒级。但 DELETE 不止要“找到”,还要“加锁 + 校验 + 写日志 + 清理二级索引”。一旦有其他事务正持有该行或附近间隙的锁(比如另一个事务刚 UPDATE 了同一行但没提交),你的 DELETE 就会卡在 LOCK WAIT 状态——此时 SHOW PROCESSLIST 显示 Updating 或 Waiting for table metadata lock,但真正原因藏在 InnoDB 锁视图里。
快速验证是否为锁等待:
- 查
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'LOCK WAIT',确认是否有等待中的事务 - 用其
TRX_WAITING_TRX_ID关联INNODB_LOCK_WAITS找出BLOCKING_TRX_ID - 再查
INNODB_TRX对应记录,重点看TRX_QUERY是否为空、TRX_STARTED是否远早于当前时间
为什么加了主键索引,DELETE 还可能锁一大片?
MySQL 在 REPEATABLE READ 隔离级别下,DELETE 即使只删一行,也会加 Next-Key Lock(记录锁 + 间隙锁)。这个锁的范围不取决于你 WHERE 的值,而取决于索引扫描路径——尤其是当你用非唯一索引或复合索引跳过最左前缀时。
例如表有复合索引 KEY idx_status_time(status, create_time),执行 DELETE FROM logs WHERE status = 2 AND create_time ,若 <code>status = 2 匹配几十万行,InnoDB 会扫描整个 status = 2 的索引段,并对每个扫描到的索引项加 Next-Key Lock,实际锁住的不只是目标行,还包括它们之间的所有间隙。结果就是:你只想删 100 行,却锁住了 5 万行的范围,后续所有涉及该范围的 INSERT/UPDATE 全部排队。
关键点:
- 主键条件能保证精准定位,但无法规避因二级索引扫描引发的范围锁
-
WHERE中用了函数(如DATE(create_time))、隐式类型转换(如id = '123'字符串对比整型)会导致索引失效,退化为全表扫描 → 锁全表 - 即使走主键,若该主键对应行在物理上分散(比如大表中频繁 DELETE/INSERT 导致页分裂),InnoDB 回表校验可见性时可能触发额外锁区间扩展
外键约束才是隐藏的“超时加速器”
这是最容易被忽略的一点:如果你的 DELETE 目标表被其他表通过外键引用,SQL Server 或 MySQL(启用 foreign_key_checks=ON)会在删除前去引用表中检查是否存在关联记录。如果引用表的外键列没有索引,就会触发全表扫描——哪怕你只删 1 行,它也要扫百万行的引用表,且全程持锁。
典型现象:
- 执行计划里出现对引用表的
ALL类型扫描(type: ALL,key: NULL) -
EXPLAIN FORMAT=JSON显示"rows_examined_per_scan"高达数十万 - 单独执行
SELECT COUNT(*) FROM ref_table WHERE fk_col = ?极慢
解决方式很简单:在引用表的外键字段上建索引。但注意,这不是“优化建议”,而是外键场景下的**强制前提**——否则每次 DELETE 都是定时炸弹。
事务日志满和批量操作没控制好是连体婴
单次 DELETE 操作若影响行数过多(比如 10 万+),每行都会生成 undo log 记录,日志文件(ib_logfile / ldf)瞬间暴涨,同时锁持有时间拉长,进一步加剧锁等待。这时候看到的“超时”,表面是 Lock wait timeout exceeded,底层可能是日志写满触发阻塞,或系统因 I/O 压力被迫降速。
安全做法不是调大 innodb_lock_wait_timeout,而是从源头切分:
- 用主键区间分批,如
DELETE FROM t WHERE id BETWEEN 10000 AND 15000,每次不超过 500 行 - 必须带
ORDER BY id LIMIT 500,避免因无序导致重复扫描 - 每次执行后检查
ROW_COUNT(),为 0 时立即退出循环 - 不要依赖
max_execution_time—— 它对DELETE完全无效
真正危险的从来不是“删得慢”,而是“删得长”。一次没控制好的批量 DELETE,可能让整个库的连接池在 3 分钟内耗尽。










