直接用delete删百万行会主动锁表,关键在于where条件需有索引、分批删除要带order by+索引列、每批后显式commit,并排查其他事务持有锁。

直接用 DELETE 删百万行,基本等于主动锁表——不是“会不会”,而是“锁多久、锁多狠”。关键不在删得快,而在让数据库只动该动的行、且不撑爆锁管理器。
WHERE 条件没索引,删多少都等于全表锁
只要 WHERE 字段没索引,InnoDB 就必须全表扫描;每扫一行加一个行锁 + 间隙锁,锁数量爆炸后自动升级为表锁。这不是 bug,是保护机制。
- 用
EXPLAIN SELECT * FROM t WHERE your_condition替代EXPLAIN DELETE(MySQL 不支持后者),确认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 单独用很危险
LIMIT 单独出现在 DELETE 里,优化器可能忽略它,或因无序导致重复删、漏删;并发执行时更易错乱。
- 正确写法:
DELETE FROM logs WHERE created_at -
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在等排他锁 - 用
SELECT * FROM information_schema.INNODB_TRX查是否有长时间未提交的事务 - 用
SELECT * FROM information_schema.INNODB_LOCK_waits看谁在等谁、等什么资源 - 别只盯着自己的 SQL,先确认有没有其他连接在持有锁、没释放
锁不是删出来的,是扫出来的、卡出来的、等出来的。最常被跳过的动作,其实是检查别人有没有在锁着你正想删的那张表。











