mysql delete按多阶段顺序执行:sql解析→权限校验→加next-key lock→查找聚簇索引记录→写undo/redo log→标记删除→提交释放锁;加锁在查找前,为防幻读,依where条件索引范围加锁。

MySQL DELETE 语句到底按什么顺序执行
不是“找到就删”,而是一套受事务、锁、日志和存储引擎严格约束的多阶段流程。InnoDB 下典型 DELETE 的实际执行顺序是:SQL解析 → 权限校验 → 加锁(next-key lock)→ 查找聚簇索引记录 → 写undo log + redo log → 标记记录为删除(purge延迟物理清除)→ 提交事务释放锁。这个顺序决定了为什么没索引会锁全表、为什么删完磁盘空间不减少、为什么大删容易卡住主从。
为什么加锁发生在“查找记录”之前而不是之后
加锁不是为了保护“要删的数据”,而是为了防止其他事务在你执行过程中插入幻行(幻读),所以 InnoDB 默认对 WHERE 条件匹配的**索引范围**提前加 next-key lock(记录锁 + 间隙锁)。这意味着:
- 用
DELETE FROM t WHERE id = 100(主键精确匹配)→ 只加 record lock,锁单行 - 用
DELETE FROM t WHERE status = 1(非唯一索引)→ 锁所有status = 1的行 + 它们之间的间隙,可能锁住上千行 - 没走索引(如
WHERE create_time 但该字段无索引)→ 全表扫描 + 全表加锁 → 极易阻塞甚至死锁
执行前务必用 EXPLAIN 确认是否命中索引,否则补索引比调优 SQL 更有效。
删完数据,磁盘空间为什么一点没少
InnoDB 不会把删掉的页还给操作系统,只是把记录标记为“已删除”,空间保留在 .ibd 文件内供后续 INSERT 复用。所以:
DELETE FROM huge_log WHERE ts 执行完,<code>du -h看 .ibd 文件大小完全不变- 真正释放空间必须重建表:
ALTER TABLE huge_log ENGINE=InnoDB或OPTIMIZE TABLE huge_log - 8.0+ 虽支持 online DDL,但
OPTIMIZE仍会触发 copy table 阶段,I/O 和 CPU 压力陡增,线上慎用 - 更稳妥的做法是分批删 + 定期归档,而非强求即时 shrink
大表删旧数据,怎么写才不拖垮数据库
直接 DELETE FROM orders WHERE created_at 是高危操作:它会生成巨量 undo log、撑爆 binlog、拉长事务、导致从库延迟数小时。安全做法是控制节奏和粒度:
- 用主键分片:例如每次删
id BETWEEN 1000000 AND 1000999,配合LIMIT 1000 - 加
SLEEP(0.1)控制节奏(应用层实现),避免连续 I/O 打满磁盘 - 优先用
SELECT id FROM ... ORDER BY id LIMIT 1000先取 ID,再DELETE WHERE id IN (...),避免重复扫描 - 确认
binlog_format = ROW,否则基于语句的 binlog 在从库可能因时间函数或非确定性排序导致不一致
最常被忽略的一点:删完别忘了 ANALYZE TABLE。统计信息过期会导致后续查询走错索引,问题可能在几天后才暴露。










