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_log8.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。统计信息过期会导致后续查询走错索引,问题可能在几天后才暴露。
