直接delete大表极危险,因每行需写undo日志、更新所有二级索引、持行锁;应分批删除(where+order by+limit+显式事务)、用分区表drop partition或rename交换表,避免锁表与日志膨胀。

为什么直接 DELETE 大表会出事
直接 DELETE FROM huge_table WHERE condition 在千万级表上大概率触发锁表、日志暴涨、主从延迟甚至 OOM。InnoDB 不是真删,而是打标记 + 写 undo/redo 日志,每删一行都得维护索引、刷盘、加锁。你看到的“删除 100 万行”,背后可能是几 GB 的日志写入和数小时的锁持有时间。
常见错误现象包括:Lock wait timeout exceeded、MySQL has gone away(连接被杀)、从库同步 lag 突增到数小时、磁盘空间突然告警。
- 没加
WHERE或条件没走索引 → 全表扫描 + 全表加锁 - 没用
LIMIT→ 单事务过大,undo log 撑爆 buffer pool - 没监控
innodb_row_lock_time_avg和Threads_running→ 直到业务报警才发现
分批删除必须带 ORDER BY 主键
只写 DELETE FROM t WHERE c 是危险的:MySQL 可能每次选不同行删,导致漏删或重复删,且无法预测下一批起点。
正确做法是显式按主键排序,确保批次间有序、可追踪、可中断:
DELETE FROM huge_table
WHERE id IN (
SELECT id FROM (
SELECT id FROM huge_table
WHERE create_time <p>注意:外层必须套一层子查询(MySQL 8.0+ 允许直接 <code>ORDER BY ... LIMIT</code>,但低版本会报错 <code>You can't specify target table for update in FROM clause</code>)。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill2334" title="MySQL"><img
src="https://img.php.cn/upload/skill/000/000/081/178900927846657.jpg" alt="MySQL" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill2334" title="MySQL" class="overflowclass">MySQL</a>
<p class="overflowclass">编写正确的MySQL查询,避免字符集、索引和锁方面的常见陷阱。</p>
</div>
<a rel="nofollow" href="/xiazai/skill2334" title="MySQL" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
- 用
id而不用create_time排序:避免时间重复导致分页偏移 - 每次执行后记录最大
id,下次从该值 + 1 开始,比单纯靠LIMIT更可靠 - 如果主键不是自增整型(比如 UUID),改用
WHERE id > ? AND create_time 范围扫描
替代方案:用 CREATE + INSERT 替换大删
当你要删掉表中 70% 以上数据时,分批删反而更慢——不如把要保留的数据导出到新表,再原子交换。这招绕过所有 delete 日志开销,空间立即释放,且不阻塞读写(只要 rename 原子性支持)。
实操步骤:
CREATE TABLE huge_table_new LIKE huge_table; INSERT INTO huge_table_new SELECT * FROM huge_table WHERE create_time >= '2020-01-01'; RENAME TABLE huge_table TO huge_table_old, huge_table_new TO huge_table; DROP TABLE huge_table_old;
关键点:
-
INSERT ... SELECT期间原表仍可读,但新表建好前写操作需暂停(或切到新表) - 确保
huge_table_new的 auto_increment、索引、分区等与原表完全一致 - 云数据库(如阿里云 RDS、AWS Aurora)对
RENAME支持良好,但某些托管服务限制DROP权限,需提前确认
生产环境执行前必做的三件事
别跳过验证环节。很多事故不是技术不对,是人没看清楚。
- 先跑
SELECT COUNT(*)和EXPLAIN FORMAT=JSON,确认rows预估准确、type是range或更好,key显示用了哪个索引 - 在从库或低峰期备库上试跑第一批次,观察
SHOW ENGINE INNODB STATUS中的 lock 信息和 purge 队列长度 - 打开 slow log 并设置
long_query_time = 0.1,实时捕获超长事务,一旦发现某批耗时突增,立刻暂停脚本
真正麻烦的从来不是语法怎么写,而是删完之后发现 binlog 没开、备份没覆盖、下游服务还缓存着旧 ID —— 这些没法靠代码自动检查,得靠人盯住。










