一条DELETE删千万行|从库延迟飙到八分钟

简介: 从一条 DELETE 删千万行、跑半小时没完、主从延迟飙到八分钟切入,拆清一条大 DELETE 其实是一个大事务:undo 只涨不缩、行锁持有到提交、ROW binlog 撑爆带宽、回滚比执行更久。给出按主键分批的脚本、批次监控与场景判断表,并说明能归档就别硬删。

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。

去年清历史订单,我写了一条 DELETE,圈了一千万行。我想着删数据嘛,减法,跑完就轻了。结果它跑了半个多小时还没完。先来的是慢查询告警。接着主从延迟飙到八分钟,磁盘占用也在往上涨。

我盯着监控有点懵。数据在减少,库怎么越来越重。后来把这条语句拆开看才明白。一条 DELETE 不是一次操作,是一千万次操作,而且全塞在同一个事务里。

删除这件事,不是减法

InnoDB 删一行,干的活比想象中多。它先把行标记成已删,再写一条 undo,记下这行原来长什么样。回滚要靠它,MVCC 也要靠它。同时写 redo,保证崩溃后能恢复。开了 ROW 格式的话,binlog 里还要记一份前镜像。

一行四笔账,一千万行就是四千万笔。磁盘上真正少掉的数据,要等后台 purge 线程慢慢清。而 purge 得等你这个事务结束。所以删得越多,先背上的东西越重。日志先落盘,数据页还没清。所以删除过程中,磁盘占用常是先涨后降。那天我看到磁盘往上走,就是这个原因。

undo:删得越多,背得越重

undo 是给回滚用的。事务不结束,它就不敢清。一千万行的 DELETE 跑半小时,这半小时里 undo 一直长,purge 一直追不上。用一条命令就能看这个水位。

SHOW ENGINE INNODB STATUS\G
-- TRANSACTIONS 段里的 History list length

这个数一直涨,就说明历史版本在堆积。undo 表空间跟着涨,而且只涨不缩。有活跃事务占着它,truncate 也做不了。磁盘告警常常不是数据造成的,是 undo 撑出来的。undo 涨太多,还有个隐性代价。别的查询走 MVCC 时,要顺着更长的版本链找可见版本。链越长,扫得越慢。你删的是历史数据,被拖慢的却是线上查询。

更麻烦的是回滚。真跑到一半你受不了,KILL 掉会话,InnoDB 得拿着 undo 一行一行翻回去。一千万行的回滚,可能比删还久。而且这期间它照样吃 IO,照样堵着锁。

锁:提交之前,谁都别碰

一条 DELETE 是一个事务,行锁要持有到提交才释放。删过的那些行,别的事务想改,只能等。要是条件字段没索引,问题更大。InnoDB 锁的是扫描到的索引记录。全表扫一遍再删,锁的范围会远超你想象的几行。RR 隔离级别下还有间隙锁,锁住的不只是行,还有行之间的空隙。我见过一条 DELETE,条件字段没索引,锁住的是一整段扫描区间。后面的 UPDATE 全在排队,等到超时,业务开始报错。所以大 DELETE 最难受的地方在锁的时间。它不只是一条语句慢,它让一批业务操作一起排队。

binlog:从库跟不上的那一段

ROW 格式下,每删一行,binlog 里就有一份前镜像。一千万行,体积很可观。主库写完还要传给从库,从库再一行行回放。主库删得快,从库追不上,延迟就出来了。那天飙到八分钟,业务读从库拿到的是旧数据。binlog 文件也在涨,备份体积跟着涨,CDC 那边也会被这波流量冲一下。写之前先记一下当前 binlog 大小,删完再比一次,涨了多少心里就有数。

SHOW BINARY LOGS;
SHOW REPLICA STATUS\G
-- 看 Seconds_Behind_Source 和 Relay_Log_Pos

那到底该怎么删

动手前先量三件事。要删多少行,现在主从延迟多少,磁盘还剩多少。行数可以用 EXPLAIN 估,别直接跑 COUNT(*)。顺手看一眼 binlog 保留策略,别删到一半把日志挤爆。

核心思路只有一句:把一个大事务拆成很多小事务。注意,总工作量没变,变的是锁、undo、binlog 分段释放。代价没有消失,只是被摊开了。分批不是让删除变快,是让它的影响能被控制。

维度 一条删完 按主键分批
锁持有 到提交才放 每批结束就放
undo 一直涨到提交 分段回收
binlog 一次性写满 分段产生
中途停止 回滚很慢 随时能停
主从延迟 冲得很高 可以控速

先按主键分批,别用 LIMIT 直接切,那样每次都要重新扫描定位。

-- 每批两千行,批间停半秒,直到影响行数为 0
DELETE FROM t_order
WHERE created_at < '2025-01-01'
ORDER BY id
LIMIT 2000;

跑完一批,SELECT ROW_COUNT() 看影响行数。大于零就歇一下再来。停多久看从库,延迟抬头就把 sleep 拉长,或者把批减小。批大小没有标准答案。我的经验是两千到五千行一批,先在从库上试。批太大单批就超过一秒,从库照样追不上。

更稳的是按主键区间走,连排序都省了。

DELETE FROM t_order
WHERE id > 8800000 AND id <= 8802000
  AND created_at < '2025-01-01';

跑的过程盯四个数。主从延迟、Threads_running、History list length、磁盘余量。哪个不对就停。停是安全的,分批本来就为了能停。不想自己写循环,pt-archiver 也能做。--limit 控批大小,--sleep 控间隔,--commit-each 保证每批提交。

哪些情况可以一条删完

不是所有删除都要分批。几万行、低峰、表不大,一条删完更省事。判断的关键是这条语句会造出多大的事务。先看它会摸到多少行,再看它要持锁多久。

场景 做法
几万行以内、低峰 一条 DELETE 可以
百万级、只删一部分 按主键分批,批间留间隔
整表清空 用 TRUNCATE,别一行行删
时间分区表、整段历史 先归档,再 DROP PARTITION
有 CDC、审计、同步链路 必须分批,盯 binlog 体积

整表清空这条特别值得说。很多人习惯 DELETE FROM t,删得慢,undo 和 binlog 还全记一遍。TRUNCATE 是重建表,快得多,日志也少。前提是这张表没有外键引用,也没人正拿着它的数据。

还有一类最容易被忽略。数据以后可能还要查,别急着删。先搬去归档表,对完账再删源数据。删错了还能回头,这才是稳。

避坑清单

别一条 DELETE 删百万级。你以为在省事,实际是在造一个超长事务。锁、undo、binlog 全跟着它一起膨胀。

分批别只加 LIMIT。没有排序或主键条件,每批都在重新扫表,越删越慢。按主键区间走,代价才是稳定的。

最后一条是我自己踩的。那次千万行 DELETE 跑不动,我直接 KILL,想着停下来就没事了。结果回滚比删还久,从库延迟反而更高。从那以后我记住,删得慢还能停,回滚开始就停不了了。现在大删除一律先分批,压根不给回滚机会。

写在最后

删除在业务眼里是减法。在 InnoDB 里,它先是一笔账。undo 记着,binlog 记着,锁压着,从库追着。行,到后面才真的消失。

所以大表删除这道题,考的不是 SQL 写得对不对。考的是你会不会把一个巨大事务拆开。拆成能停、能看、能收手的节奏。数据可以分批走。代价不能一笔勾销。

你们清理历史数据,是一次删完还是分批?有没有被回滚坑过?评论区聊聊。

我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

相关文章
|
4月前
|
canal 关系型数据库 MySQL
MySQL LIKE查询太慢?手把手搭建Elasticsearch站内搜索
本文详解MySQL模糊搜索性能瓶颈及Elasticsearch全文检索解决方案:剖析`LIKE &#39;%关键词%&#39;`全表扫描原理,对比MySQL全文索引局限,深入讲解倒排索引机制,并实战演示Logstash/Canal数据同步、IK中文分词、高亮搜索等核心环节,助你构建毫秒级站内搜索。(239字)
|
4月前
|
消息中间件 SQL 监控
分布式事务最佳实践:TCC空回滚、Saga补偿风暴与选型建议
讲述分布式事务从2PC到TCC再到Saga的踩坑经历,解析强一致性与最终一致性的取舍、TCC的空回滚悬挂幂等问题、Saga的补偿风暴,附选型建议和避坑清单
|
4月前
|
SQL 中间件 关系型数据库
读写分离中间件怎么选?ProxySQL 落地踩坑与选型对比
ProxySQL是一款轻量高性能MySQL中间件,原生支持读写分离、自动故障切换与查询路由,相比MyCAT、ShardingSphere更专注、易用、低损耗,特别适合仅需主从读写分离的场景。
|
4月前
|
存储 运维 关系型数据库
分布式数据库架构演进:从集中式到分布式,三大路线一次讲清楚
本文深入浅出解析分布式数据库选型逻辑:厘清集中式与分布式适用边界,剖析分片、事务、一致性三大技术难点,对比分库分表、原生分布式、共享存储集群三类架构。务实提醒——分布式非银弹,够用才是硬道理。
|
4月前
|
SQL 关系型数据库 MySQL
SQL代码审查指南:命名规范+10大反模式+四维检查清单,一篇全搞定
数据库小学妹带你攻克SQL规范难题!从命名、格式到10大反模式(如SELECT*、隐式转换、ORDER BY RAND等),结合真实踩坑案例,详解可读、可维护、高性能的SQL写法,并提供SQL Review四维审查清单与团队落地方法,助你写出工业级质量SQL。
|
4月前
|
SQL 算法 关系型数据库
数据库大表ALTER最佳实践:pt-osc、gh-ost原理与生产调优
数据库小学妹分享大表ALTER避坑指南:详解Instant DDL(8.0+毫秒加字段)、pt-osc(触发器同步)与gh-ost(binlog解析)三大方案,剖析MDL锁、COPY/INPLACE算法及磁盘空间陷阱,助你安全完成在线DDL变更。(239字)
|
4月前
|
存储 人工智能 多模数据库
大模型时代数据库角色转型实战:从RAG检索增强到AI Agent数据底座的架构思考
本文探讨大模型时代数据库角色的深刻转变:从数据存储转向AI底座。详解RAG如何依赖向量数据库实现知识增强,Agent如何依托数据库实现记忆持久化与上下文管理,以及多模数据库如何支撑AI Agent的工具调用与执行。DBA需扩展向量检索、多模存储等新能力,而非被替代。(239字)
|
4月前
|
SQL 运维 自然语言处理
国产向量数据库有哪些?两大技术流派深度对比与选型指南
向量数据库是2026年数据库领域增长最快的细分赛道之一。本文从RAG应用和企业知识库的实际需求出发,系统梳理国产向量数据库的两大技术流派——独立向量数据库与融合型向量数据库,深入对比两者的架构差异、适用边界和选型逻辑。
|
4月前
|
SQL 关系型数据库 MySQL
MySQL慢查询治理最佳实践:从定位到优化再到上线的完整方案
本文系统讲解MySQL慢查询排查与优化全流程:从`SHOW PROCESSLIST`快速定位问题,到`pt-query-digest`精准识别高耗时SQL;结合`EXPLAIN`深度分析执行计划(重点关注type、Extra、rows);覆盖六大高频场景(深分页、SELECT*、COUNT(*)、多表JOIN等)的实战优化方案;并强调生产规范——大表加索引用pt-osc、上线前必验执行计划、关键参数调优要点。重流程、轻工具,助你从容应对线上性能危机。(239字)
|
4月前
|
SQL 监控 关系型数据库
MySQL连接数管理最佳实践:从Too many connections应急到长期治理
本文详解MySQL“Too many connections”故障的应急排查与根治方案:30秒快速评估、杀Sleep连接止血、动态调参救急;深入分析慢查询、连接泄漏、短连接冲击、配置过小四大根因;附诊断脚本、监控告警规则及连接池最佳实践,助你从容应对生产危机。

热门文章

最新文章