分批删除需避免全表扫描和锁表,必须为where条件字段建有效索引;limit不带order by易致重复扫描,推荐用主键范围(如id > last_id)替代limit实现高效断点续删。

直接删几百万行大概率会卡死业务,必须分批;但光加 LIMIT 不够,不控制扫描范围、不加索引、不控节奏,照样锁表、拖垮主库。
为什么 LIMIT 10000 还是会锁很久?
MySQL 的 DELETE ... WHERE condition LIMIT N 看似安全,但实际执行时仍可能扫描全表匹配行——尤其当 condition 字段没索引,或优化器选错执行计划。InnoDB 会对所有扫描到的行(含间隙)加锁,不是只锁最终删掉的那 N 行。
- 必须确保
WHERE条件字段有有效索引(比如create_time或id),否则每次都是全表扫 + 文件排序 -
LIMIT不带ORDER BY时,MySQL 可能随机选匹配行,导致同一批数据反复扫描、重复删除失败 - 推荐写法:
DELETE FROM logs WHERE create_time ,让扫描路径稳定、可预测
用 id > last_id 比 LIMIT 更可控
对主键连续或近似连续的表(如自增 id),用范围条件替代 LIMIT 能彻底避开扫描偏移开销,也更容易断点续删。
- 先查最小/最大要删的
id:SELECT MIN(id), MAX(id) FROM logs WHERE create_time - 循环执行:
DELETE FROM logs WHERE id BETWEEN 100000 AND 100999 AND create_time - 每次递增区间,避免漏删或重删;
BETWEEN走索引快,且不依赖优化器对LIMIT的实现细节
删完不释放空间?这不是 bug,是 InnoDB 的设计
DELETE 后 data_length 不变、磁盘占用没降,是正常现象。InnoDB 只标记页内空间为“可复用”,不会立刻归还给操作系统。
- 别靠
df -h判断是否删成功,看SELECT COUNT(*)或TABLE_ROWS(估算值)更准 -
OPTIMIZE TABLE或ALTER TABLE ... ENGINE=InnoDB才能真正收缩文件,但它们会锁表、重建,生产环境慎用 - 如果表已启用独立表空间(
innodb_file_per_table=ON),长期看空间会被后台线程逐步回收,无需人工干预
应用层脚本里最容易被忽略的三件事
DBA 写 SQL 很熟,但批量删常由应用代码驱动——这里漏掉一个点,就可能压垮数据库。
- 每次
DELETE后必须加SLEEP(0.1)(应用层控制),否则瞬间发几十个事务,CPU 和 I/O 直接飙红 - 监控
SHOW PROCESSLIST或information_schema.INNODB_TRX,确认事务时长始终在 1 秒内,超时说明批次太大或索引失效 - 别用
OFFSET分页式删除(如LIMIT 1000 OFFSET 10000),越往后越慢,且无法断点续删
真正难的不是写 DELETE 语句,而是让每一批都落在 MySQL 的“舒适区”:索引可用、范围明确、事务轻量、节奏可控。一旦某次执行变慢,优先检查索引是否失效、主从延迟是否拉大、以及 binlog_format 是否为 ROW(否则从库重放可能更慢)。











