返回首页

MySQL 磁盘 80% 告警,碎片 52G,我差点 TRUNCATE 掉正在写入的业务表

MySQL 磁盘 80% 告警,碎片 52G,我差点 TRUNCATE 掉正在写入的业务表

大家好,我是老张。

连着写了三天博客安全系列,今天换个话题——生产踩坑。

上周写个人博客后台写嗨了,差点在生产环境捅个大篓子。

⚠️ 声明:本文基于真实生产环境排查案例编写,已对 IP 地址、实例名、库名/表名、密码等敏感信息做脱敏处理,命令可直接复制执行。

"磁盘 80% 告警,碎片 52G,TRUNCATE 一下不就完了?"

我相信绝大多数运维人的第一反应都是这个。说实话,我当时也这么想的。

直到我多做了一个操作——查了一下数据的最晚写入时间

然后冷汗都下来了。


📊 告警来了

某天下午,监控弹出一条告警:Meta MySQL 磁盘使用率超过 80%

进云平台控制台一看,实例规格 4C16G,磁盘 150GB 本地 SSD,用了 120G。主节点 80.07%,备节点 79.72%,同步正常。

先看磁盘分拆:数据 119.6G + Binlog 509M + 日志 5M。

Binlog 才 500M——不是 Binlog 堆积,数据文件有问题。


🔍 排查:差额到底在哪?

SSH 到管理节点,进 Pod 直连 MySQL:

# SSH 到管理节点
ssh <管理节点IP>

# 进 Pod 直连 MySQL
kubectl exec -it <pod名> -n <namespace> -- mysql -uroot -p

先 SQL 统计各库大小:

SELECT table_schema AS '库名',
       ROUND(SUM(data_length + index_length) / 1024/1024/1024, 2) AS 'SQL统计(GB)'
FROM information_schema.tables
WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys')
GROUP BY table_schema ORDER BY 2 DESC;
SQL 统计
库A16.8G
库B8.3G
库C5.1G
库D5.1G

所有库加起来才 ~44G。但磁盘用了 120G——差了 76G

再用磁盘实际占用对比 SQL 统计,定位差额:

# 进 Pod 的 mysql 容器,直接 du 各库目录
kubectl exec -it <pod名> -n <namespace> -c mysql -- bash -c \
  "du -sh /var/lib/mysql/*/ 2>/dev/null | sort -rh | head -10"
库目录磁盘实际SQL 统计碎片
库B60G8.3G51.7G 🔴
库A23G16.8G6.2G
库C7.6G5.1G2.5G
库D6.8G5.1G1.7G

一个库吃掉了 52G 碎片。

精确量化:data_free 排名

要找碎片最大的表,最快的方式是看各表的 .ibd 文件实际大小,再跟 SQL 统计对比:

# 进 Pod,查目标库下所有 .ibd 文件大小
kubectl exec -it <pod名> -n <namespace> -c mysql -- bash -c \
  "du -sh /var/lib/mysql/<库名>/*.ibd 2>/dev/null | sort -rh | head -15"

结果跟 du 数据吻合——8 张表的 .ibd 文件远大于 SQL 统计值。下一步用 data_free 精确量化。

DBA 要求用 information_schema.TABLESdata_free 字段——这个字段统计的是 InnoDB 内部已分配但未使用的空间,比 du 更准确:

SELECT
  CONCAT(table_schema, '.', table_name) table_name,
  ROUND((data_length + index_length) / 1024/1024, 2) AS '数据大小(M)',
  ROUND(data_free / 1024/1024, 2) AS '碎片(M)',
  (100*(data_free/(data_length+index_length+data_free))) AS '浪费比例%'
FROM information_schema.TABLES
WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys')
ORDER BY data_free DESC LIMIT 8;

结果触目惊心:

实际数据碎片总占用浪费比例
表11.4G9.2G10.6G86.3%
表2666M7.6G8.3G92.0%
表3152M6.6G6.8G97.8%
表41.1G5.2G6.4G81.7%
表5103M4.3G4.4G97.7%
表669M4.0G4.1G98.3%
表783M3.2G3.3G97.5%
表82.6G1.9G4.6G41.0%

8 张表合计碎片约 46G,大部分浪费比例 80%+。其中表 3 最夸张——实际数据只有 152M,碎片却有 6.6G,浪费 97.8%。

看到这个数据,我脑子里的第一反应不是"这表还在用吗?"——而是"这表居然能有这么多碎片?"

98% 的浪费率,大脑会直接下结论:这是张废弃表,没人管了

更何况,业务方上周刚跟我说过"库B那几张历史表没用了,有空清一下"

到这一步,直觉告诉我:TRUNCATE 掉就完事了


⚠️ 转折:多做了一个操作

正准备提单 TRUNCATE,突然想到一个问题——这表还在用吗?

先看表结构,找时间字段:

SHOW FULL COLUMNS FROM 库B.1;

常见时间字段叫 created_timegmt_createcreate_time,找到后查数据时间范围:

SELECT
  MIN(created_time) AS 最早创建,
  MAX(created_time) AS 最晚创建,
  COUNT(*) AS 总行数
FROM 库B.1;

结果:

最早创建: 2026-07-25 23:15:02
最晚创建: 2026-08-04 11:00:31   ← 就是现在!
总行数:   202,257

最晚创建时间就是查询时刻——这张表是活的,实时高频率写入中。20 万行记录仅 10 天数据,9.2G 碎片来自频繁 INSERT/UPDATE/DELETE,InnoDB 来不及回收。

如果刚才直接 TRUNCATE,几 万行业务数据就没了

😱 冷汗都下来了。


💡 决策矩阵

面对碎片表,判断能不能 TRUNCATE 的关键不是碎片大小,而是数据时间范围

时间特征判断方案
最早 > 6 个月前,最晚也是 6 个月前冷数据TRUNCATE(确认业务已停)
最早 < 1 个月前,最晚 = 此刻活跃写入OPTIMIZE,不能 TRUNCATE
时间跨度短 + 行数多高频写入碎片低峰期 OPTIMIZE

🛠️ OPTIMIZE 执行:比预估快 20 倍

方案确定:逐表 OPTIMIZE TABLE,变更窗口定在下午 6 点。

-- 逐表执行,每张跑完再跑下一张
OPTIMIZE TABLE 库B.1;
OPTIMIZE TABLE 库B.2;
OPTIMIZE TABLE 库B.3;
OPTIMIZE TABLE 库B.4;
OPTIMIZE TABLE 库B.5;
OPTIMIZE TABLE 库B.6;
OPTIMIZE TABLE 库B.7;
OPTIMIZE TABLE 库B.8;

预估:表 1 有 10.6G 总占用,按经验得跑十几分钟。8 张表下来至少 1 小时。

实际执行

#碎片实际耗时结果
1表81.9G1m39s
2表73.2G0.6s
3表64.0G2.9s
4表54.3G3.4s
5表45.2G6.4s
6表36.6G5.3s
7表27.6G15.6s
8表19.2G9.7s
合计~46G~3 分钟

8 张表跑完,总共不到 3 分钟

为什么这么快?因为表虽然总占用 8-10G,但 86-98% 是碎片。OPTIMIZE 只拷贝有效数据——10G 的表实际数据只有 1.4G,搬这点东西当然快。


🎉 最终效果

磁盘使用率:80% → 53%,回收约 40G。

节点磁盘数据Binlog同步
52.93%78.8G583M0ms
52.92%78.7G560M1s

❓ 读者可能会问:碎片这么大了为什么不清?

很简单:之前没人知道。

  • 业务方:表还在正常写入,不知道有碎片问题
  • 开发:只管用业务逻辑,不会定期查 data_free
  • DBA:管几百个实例,顾不上这种"还没爆雷"的碎片
  • 运维:只看磁盘使用率,80% 告警才拉群

这就是运维的日常——问题不到炸出来的那一刻,没人会主动关注。

如果今天不是我多查了一步 MAX(created_time),这张表就没了。


💡 老张的经验总结

✅ 做对了什么

  1. 先排除 Binlog——控制台分拆一看 500M,直接跳过不必要的排查
  2. du vs SQL 双对比——磁盘 60G、SQL 8G,差额就是碎片,定位精准
  3. data_free 精确量化——比 du 估算更准,而且是 DBA 认可的标准指标
  4. 数据活跃度验证——这是整个排查中最关键的一步。碎片再大也不能无脑 TRUNCATE
  5. OPTIMIZE 比预估快——碎片率越高跑得越快,因为只搬有效数据

⚠️ 如果再遇到这种事

  1. 先查时间范围,再做决策——不管你多确定这表没用了,不管业务方跟你说过多少次"这表可以清",跑一条 SELECT MIN/MAX 只要 0.1 秒。别人说的"没用",跟数据库里的"真没用",中间隔了 几万行数据
  2. 定时统计 data_free——可以加到巡检脚本里,碎片率 > 50% 就告警,别等 80% 了再处理
  3. 碎片率越高的表,OPTIMIZE 越快——这个反直觉的事实,记下来
  4. "业务方说没用"不算数,"最后一条写入是半年前"才算数——口头承诺不可信,数据库里的时间戳才是铁证

🔧 碎片排查命令速查

-- 1. 查看各库碎片 Top
SELECT
  table_schema AS '库',
  ROUND(SUM(data_free) / 1024/1024/1024, 2) AS '碎片(GB)'
FROM information_schema.TABLES
WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys')
GROUP BY table_schema ORDER BY 2 DESC;

-- 2. 查看某库表碎片排名(含浪费比例)
SELECT
  table_name AS '表名',
  ROUND((data_length + index_length) / 1024/1024, 2) AS '数据(M)',
  ROUND(data_free / 1024/1024, 2) AS '碎片(M)',
  ROUND(100 * data_free / (data_length + index_length + data_free), 1) AS '浪费%'
FROM information_schema.TABLES
WHERE table_schema = '你的库名'
ORDER BY data_free DESC LIMIT 10;

-- 3. ⚠️ 关键:查数据时间范围(决定能不能 TRUNCATE)
SELECT MIN(created_time), MAX(created_time), COUNT(*) FROM 你的库.你的表;

-- 4. OPTIMIZE(低峰期执行)
OPTIMIZE TABLE 你的库.你的表;

🎯 适合谁看?

场景推荐程度
MySQL 磁盘告警不知道怎么排查⭐⭐⭐⭐⭐
遇到过碎片但不确定 TRUNCATE 还是 OPTIMIZE⭐⭐⭐⭐⭐
想知道 data_free 怎么用⭐⭐⭐⭐
纯开发不碰数据库运维⭐⭐

💬 聊聊你的经历

  1. 你遇到过 MySQL 碎片占磁盘一半以上的情况吗?
  2. TRUNCATE 和 OPTIMIZE 之间你一般怎么选?有没有踩过坑?
  3. 你们会在巡检里加 data_free 监控吗?
A

Admin

用文字记录生活与思考。

评论 (0)
暂无评论,来抢沙发吧
MySQL 磁盘 80% 告警,碎片 52G,我差点 TRUNCATE 掉正在写入的业务表 | 山外云的Vlog