DELETE大表不锁表的关键是避免锁升级:WHERE条件必须有索引,分批删除需ORDER BY索引列+LIMIT,每批后COMMIT,并排查隐性锁源。

DELETE 大表不锁表,前提是它根本没机会升级成表锁——关键不是“怎么删快”,而是“怎么让数据库只动该动的行,且别撑爆锁管理器”。
WHERE 条件没索引,删多少都等于全表锁
只要 WHERE 字段没索引,InnoDB 就必须全表扫描;每扫一行加一个行锁 + 间隙锁,锁数量爆炸后自动升级为表锁。这不是 bug,是 InnoDB 的保护机制。
常见错误现象:SHOW PROCESSLIST 显示状态为 Updating 或 Locked,同时其他查询大面积等待 Waiting for table metadata lock。
- 用
EXPLAIN SELECT * FROM t WHERE your_condition替代EXPLAIN DELETE(MySQL 不支持直接 EXPLAIN DELETE),确认type是range或ref,不是ALL - 别在
WHERE里写DATE(created_at) = '2024-01-01'—— 函数导致索引失效 - 别写
WHERE user_id = '123'(user_id是INT)—— 隐式类型转换跳过索引 - 加索引要选低峰期,MySQL 5.6+ 可用
ALGORITHM=INPLACE减少锁表时间
分批删必须带 ORDER BY + 主键/索引字段
LIMIT 单独用在 DELETE 里很危险:优化器可能忽略它,或因无序导致重复删、漏删;并发执行时更易错乱。
正确姿势是锁定扫描范围,靠主键或有索引的字段推进:
- 写法必须是:
DELETE FROM logs WHERE created_at 100000 ORDER BY id LIMIT 1000 -
ORDER BY字段必须是索引列(最好是主键),否则触发filesort,反而更慢、更耗资源 - 别用
LIMIT 10000, 5000这种 offset 分页式写法——偏移越大越慢,且并发下容易跳过数据块 - 每次删完查
ROW_COUNT(),返回 0 就停;别硬设循环 100 次
事务大小和提交节奏比 LIMIT 数值更重要
删 1000 行但包在一个未提交事务里,和删 10 万行但每 1000 行就 COMMIT,锁影响天差地别。锁释放时机取决于事务结束,不是语句执行完。
- 每批
DELETE后必须显式COMMIT(或确保autocommit=1) - 单次删建议控制在 500–5000 行之间:太小(如 100)事务开销占比高;太大(如 10000)仍可能触发锁升级或日志写满
- 加
DO SLEEP(0.01)是为了缓冲 CPU 和 I/O 压力,尤其在从库或资源紧张机器上;不是可有可无的装饰 - 删完立刻跑
ANALYZE TABLE t更新统计信息,避免后续查询走错执行计划
真正容易被忽略的“隐形锁源”
你加了索引、分了批、限了速,还是卡——大概率不是 SQL 写得不对,而是别的进程在“偷偷锁着表”。这类问题不报错,但会让所有优化失效。
- 监控脚本每 5 秒跑一次
SELECT COUNT(*) FROM t,拿的是共享锁,而你的DELETE在等排他锁 -
binlog_format=STATEMENT下,大事务重放极慢,主库刚提交,从库已延迟数小时,看起来像“一直卡着” - 某个未关闭的事务里执行了
SELECT ... FOR UPDATE,锁还挂着,DELETE一直在等 - SQL Server 场景下,
TRUNCATE被阻塞常因有人正用SELECT查询该表——它需要 SCH-M 锁,和几乎所有锁互斥
删之前先 SHOW ENGINE INNODB STATUS\G 看锁等待链,比反复调 LIMIT 数值有用得多。

















