首页 文章 精选 留言 我的

精选列表

搜索[MySQL],共10000篇文章
优秀的个人博客,低调大师

MySQL大表DELETE为什么慢?千万行背后的undo、锁与延迟

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。 去年清历史订单,我写了一条 DELETE,圈了一千万行。我想着删数据嘛,减法,跑完就轻了。结果它跑了半个多小时还没完。先来的是慢查询告警。接着主从延迟飙到八分钟,磁盘占用也在往上涨。 我盯着监控有点懵。数据在减少,库怎么越来越重。后来把这条语句拆开看才明白。一条 DELETE 不是一次操作,是一千万次操作,而且全塞在同一个事务里。 ## 删除这件事,不是减法 InnoDB 删一行,干的活比想象中多。它先把行标记成已删,再写一条 undo,记下这行原来长什么样。回滚要靠它,MVCC 也要靠它。同时写 redo,保证崩溃后能恢复。开了 ROW 格式的话,binlog 里还要记一份前镜像。 一行四笔账,一千万行就是四千万笔。磁盘上真正少掉的数据,要等后台 purge 线程慢慢清。而 purge 得等你这个事务结束。所以删得越多,先背上的东西越重。日志先落盘,数据页还没清。所以删除过程中,磁盘占用常是先涨后降。那天我看到磁盘往上走,就是这个原因。 ## undo:删得越多,背得越重 undo 是给回滚用的。事务不结束,它就不敢清。一千万行的 DELETE 跑半小时,这半小时里 undo 一直长,purge 一直追不上。用一条命令就能看这个水位。 ```sql 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 大小,删完再比一次,涨了多少心里就有数。 ```sql SHOW BINARY LOGS; SHOW REPLICA STATUS\G -- 看 Seconds_Behind_Source 和 Relay_Log_Pos ``` ## 那到底该怎么删 动手前先量三件事。要删多少行,现在主从延迟多少,磁盘还剩多少。行数可以用 EXPLAIN 估,别直接跑 COUNT(*)。顺手看一眼 binlog 保留策略,别删到一半把日志挤爆。 核心思路只有一句:把一个大事务拆成很多小事务。注意,总工作量没变,变的是锁、undo、binlog 分段释放。代价没有消失,只是被摊开了。分批不是让删除变快,是让它的影响能被控制。 | 维度 | 一条删完 | 按主键分批 | | --- | --- | --- | | 锁持有 | 到提交才放 | 每批结束就放 | | undo | 一直涨到提交 | 分段回收 | | binlog | 一次性写满 | 分段产生 | | 中途停止 | 回滚很慢 | 随时能停 | | 主从延迟 | 冲得很高 | 可以控速 | 先按主键分批,别用 LIMIT 直接切,那样每次都要重新扫描定位。 ```sql -- 每批两千行,批间停半秒,直到影响行数为 0 DELETE FROM t_order WHERE created_at 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 写得对不对。考的是你会不会把一个巨大事务拆开。拆成能停、能看、能收手的节奏。数据可以分批走。代价不能一笔勾销。 你们清理历史数据,是一次删完还是分批?有没有被回滚坑过?评论区聊聊。 我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

用户登录
用户注册