InnoDB DELETE未走索引时等价于锁全表,因其被迫全表扫描并对每行加行锁+间隙锁;根本原因是WHERE字段无索引、索引失效或优化器误判。

直接结论:MySQL 或 SQL Server 中 DELETE 大量数据锁表,根本原因不是语句写错了,而是事务太大、索引缺失、或锁升级触发了表级阻塞。解决核心是“拆小 + 控制 + 验证”,不是加 hint 或调参数。
为什么 DELETE 会升级成表锁
InnoDB 默认行锁,但当你执行 DELETE FROM t WHERE ts 且 <code>ts 字段没索引时,优化器被迫全表扫描——每扫一行都加行锁,锁管理开销爆炸,InnoDB 会自动升级为表锁。SQL Server 同理,尤其带全文索引的表,删除过程会在嵌套事务里同步清理索引,持锁时间远超预期。
- 无索引的 WHERE 条件 = 每次删都全表扫描 + 行锁堆积 + 锁升级高概率
- 单次删百万行 = 一个大事务写满 undo log、redo log、binlog,主从延迟飙升
- 长事务未提交(哪怕只是 SELECT)也会让 DELETE 等待,形成连锁阻塞
MySQL 分批删除必须带 ORDER BY 和索引
DELETE ... LIMIT 不是万能的,不配合 ORDER BY 和有效索引,可能漏删、重复删,甚至优化器忽略 LIMIT。
- 正确写法:
DELETE FROM logs WHERE created_at -
id必须是主键或有索引,否则ORDER BY触发 filesort,反而更慢 - 别用
LIMIT 10000,5000这种 offset 分页式写法——偏移越大越慢,且并发下易跳过数据 - 删完立刻查
SHOW PROCESSLIST,看到Locked或Waiting for table metadata lock就该加休眠
SQL Server 删除后空间不释放怎么办
DELETE 只是标记行删除,不释放数据页。堆表(无聚集索引)尤其明显,后续 INSERT 可能直接报 “filegroup is full”。
- 临时解法:
DELETE FROM t WITH (TABLOCK),强制加表锁,删完立即释放页面(仅限未启用 RCSI 的库) - 彻底解法:改用
TRUNCATE TABLE t(注意不能带 WHERE,且需 DDL 权限) - 长期建议:给表加聚集索引,让删除后空间可被后续 INSERT 复用
- 别依赖
DBCC SHRINKFILE——它会造成严重碎片,只在磁盘告急时救急
真正容易被忽略的点
很多人试过分批、加索引、调 LIMIT,还是卡——问题往往出在“看不见的依赖”上:比如某个监控脚本每 5 秒跑一次 SELECT COUNT(*) FROM t,它拿的是共享锁,而你的 DELETE 在等排他锁;又或者 binlog_format=STATEMENT 模式下,大事务重放极慢,主库还没删完,从库已延迟 2 小时。这些不会报错,但会让整个方案失效。

















