磁盘98%告警,ibdata1占了320G:五个大户排查记录

简介: 以凌晨磁盘告警事故切入,逐一排查binlog、InnoDB表空间、undo日志、临时表、慢日志五个磁盘大户,覆盖MySQL 8.0的undo表空间管理和TempTable引擎变化,附自动清理脚本与监控配置

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

凌晨两点四十五分,手机被告警短信叫醒。磁盘使用率98%,MySQL所在分区只剩不到两个G。第一反应是df -h确认情况。

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3       500G  490G   10G  98% /var/lib/mysql

确认确实满了,接着用du -sh逐层下钻,找出到底是谁在吃盘。

du -sh /var/lib/mysql/*
320G    ibdata1
85G     mysql-bin.000087
42G     mysql-bin.000086
38G     slow.log
55G     app_db

看到结果我倒吸一口凉气。ibdata1共享表空间占了320G,binlog两个文件加起来127G,慢日志38G。这一晚我没睡,但把五个吃硬盘的大户一个个查清楚了。今天完整写出来,希望你遇到同样情况能照着排一遍就能定位,少走弯路少踩坑。

第一个吃硬盘大户:binlog暴胀

binlog本身没有总量上限,约束它的只有过期时间。如果expire_logs_days设了30天,生产写入量大,30天的binlog轻轻松松上百G。我那次更极端,一个批量导入任务跑了6小时,单小时就写了40G,两个文件127G就是这么来的。

这里要澄清一个容易误解的点。max_binlog_size控制的是单个文件大小,到阈值会自动切新文件,但一个事务不会被拆到两个文件,所以单个binlog可能略大于设定值。真正决定binlog总量的,还是过期时间。

排查方法

-- 查看binlog保留策略
SHOW VARIABLES LIKE 'expire_logs_days';
-- 8.0+ 用这个
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';

-- 查看当前binlog文件大小
SHOW BINARY LOGS;

清理方法

-- 清理指定时间之前的binlog
PURGE BINARY LOGS BEFORE '2026-08-01 00:00:00';

-- 或者清理到指定文件
PURGE BINARY LOGS TO 'mysql-bin.000080';

别用rm直接删binlog文件。MySQL的binlog index文件里记录了所有binlog文件名,rm删了文件但index不会更新,下次启动就会报错。PURGE命令会同步更新index,这是它的价值所在。

治本方案

# my.cnf
# 8.0以下设置天数,8.4起该参数已彻底移除
expire_logs_days = 7
# 8.0+用秒数,8.4后只剩这一个参数
binlog_expire_logs_seconds = 604800
# 单文件最大1G,超过自动切新文件
max_binlog_size = 1G

保留7天是常见起点,核心系统可以设到14天。有主从复制的系统要先确认从库追平再清理,否则从库要拉取的binlog被删了,主从就断了。

-- 在主库上确认从库位点
SHOW SLAVE HOSTS;
-- 确保从库的Exec_Master_Log_Pos接近主库的binlog位点

第二个吃硬盘大户:InnoDB共享表空间

ibdata1是InnoDB的共享表空间,存的是change buffer、doublewrite buffer这些全局结构。5.6之前连表数据和索引也塞在这里,5.6之后可以开innodb_file_per_table让每张表独立成.ibd文件,undo在8.0也默认独立出去了。

最坑的一点是,ibdata1一旦撑大就缩不回来。你DELETE一百万条数据,表里数据是少了,但磁盘占用一点不减。DELETE只是把page标记为可复用,空间还留在表空间里,不会归还操作系统。想真正缩回来,只能重建。

排查方法

-- 查看表空间大小
SELECT table_name,
  ROUND(data_length/1024/1024, 2) AS data_mb,
  ROUND(index_length/1024/1024, 2) AS index_mb,
  ROUND(data_free/1024/1024, 2) AS free_mb
FROM information_schema.tables
WHERE table_schema = 'app_db'
ORDER BY data_free DESC;

-- 查看ibdata1实际大小
SELECT file_name, tablespace_name,
  ROUND(total_extent_size/1024/1024/1024, 2) AS size_gb
FROM information_schema.FILES
WHERE tablespace_name = 'innodb_system';

上面SQL里的data_free就是碎片空间,它只说明有多少page被标记了可复用,不表示这些空间已经还给操作系统。

治本方案:独立表空间

# my.cnf
innodb_file_per_table = ON

这个参数MySQL 5.6+默认就是ON,但老系统可能还是OFF。开启后每张表有自己的.ibd文件,DELETE后虽然ibd文件不会自动缩小,但至少可以用OPTIMIZE TABLE回收空间。

-- 回收单表碎片
OPTIMIZE TABLE t_order;
-- 本质是重建表,期间会锁表

OPTIMIZE TABLE本质是重建表,期间会锁表,而且重建需要额外的临时空间,表里数据多的话反而可能把盘写爆。所以大表别直接OPTIMIZE,用gh-ost或pt-online-schema-change在线重建更稳妥。

如果ibdata1已经大到几百G,唯一办法是逻辑导出、重建数据库、再导入回去。

# 全量导出
mysqldump --all-databases --routines --triggers > /tmp/full.sql
# 停库
systemctl stop mysqld
# 清空数据目录
rm -rf /var/lib/mysql/*
# 重新初始化
mysqld --initialize
# 导入
mysql < /tmp/full.sql

这个过程要停机,所以最好在设计阶段就开启innodb_file_per_table。

第三个吃硬盘大户:undo日志膨胀

undo日志存的是事务回滚需要的旧版本数据,也是MVCC读视图要用的历史版本。长事务不提交,undo就一路增长。

有个真实场景:一个报表查询开了事务,跑了40分钟。这40分钟里所有变更数据的undo都不能清理,undo表空间直线增长。原因在于InnoDB的purge线程只会清理不再被任何读视图引用的旧版本,长事务的读视图一直存在,purge就一直滞后。

排查方法

-- 查看长事务
SELECT trx_id, trx_state, trx_started,
  trx_query, trx_mysql_thread_id,
  TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec
FROM information_schema.INNODB_TRX
WHERE trx_started < NOW() - INTERVAL 60 SECOND
ORDER BY duration_sec DESC;

-- 查看undo表空间大小
SELECT file_name, tablespace_name,
  ROUND(total_extent_size/1024/1024, 2) AS size_mb
FROM information_schema.FILES
WHERE tablespace_name LIKE 'innodb_undo%';

监控上还要盯history list length,这个值直接反映purge是否滞后,数值长期偏高说明有长事务在拖后腿。

清理方法

杀掉长事务。

-- 找到线程ID后直接KILL
KILL 12345;

杀掉后undo日志会自动被purge线程清理,但空间不会释放回操作系统,只是标记为可复用。

治本方案

# my.cnf - 8.0+ undo表空间自动截断
innodb_undo_log_truncate = ON
innodb_max_undo_log_size = 1G
innodb_purge_rseg_truncate_frequency = 128

8.0默认就会创建两个独立undo表空间(undo_001和undo_002),不用再像老版本那样手工配innodb_undo_tablespaces,这个参数8.0.14起已经废弃,8.0.21后直接移除,额外的undo表空间用CREATE UNDO TABLESPACE语句管理。

开启undo自动截断后,undo表空间超过1G会自动收缩。innodb_undo_log_truncate在8.0里默认就是ON,通常不用改。innodb_purge_rseg_truncate_frequency控制purge多少次才尝试截断一次,设小点截断更勤快,但会带来额外开销。在线截断从5.7就开始支持,5.6及以下才需要重建实例。

第四个吃硬盘大户:临时表

临时表分两种,内存临时表和磁盘临时表。内存临时表超过tmp_table_size或max_heap_table_size限制后,会自动转成磁盘临时表,落在tmpdir目录。8.0有个关键变化:内存内部临时表的默认引擎从MEMORY换成了新的TempTable引擎,磁盘内部临时表则固定用InnoDB的temp tablespace。TempTable有独立的内存上限参数temptable_max_ram,超了照样落盘。8.0之前磁盘临时表还是MyISAM引擎,8.0起才统一成InnoDB。

大表的GROUP BY和ORDER BY走不了索引,就会用filesort加磁盘临时表。一个百万行的表做GROUP BY,磁盘临时表可能占到十几G。判断SQL会不会产生磁盘临时表,看EXPLAIN的Extra列,出现Using temporary就是用了临时表,Using filesort就是走了文件排序。

排查方法

-- 查看临时表配置
SHOW VARIABLES LIKE 'tmp_table_size';
SHOW VARIABLES LIKE 'max_heap_table_size';

-- 查看tmpdir位置
SHOW VARIABLES LIKE 'tmpdir';

-- 查看磁盘临时表数量
SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';
-- 和总临时表数量对比
SHOW GLOBAL STATUS LIKE 'Created_tmp_tables';

磁盘临时表和总临时表的比值长期超过5%,就说明有SQL在频繁落盘,该优化了。

清理方法

MySQL会在查询结束后自动清理临时表,但如果查询本身卡死了,临时文件就一直占着空间。

-- 找到正在执行的查询
SELECT id, user, host, db, command, time, state,
  ROUND(LENGTH(info)/1024, 2) AS info_kb
FROM information_schema.PROCESSLIST
WHERE command != 'Sleep'
ORDER BY time DESC;

-- 杀掉卡死的查询
KILL 67890;

治本方案

# my.cnf
tmp_table_size = 128M
max_heap_table_size = 128M
# tmpdir指向独立的磁盘分区
tmpdir = /tmp/mysql_tmp

给tmpdir单独挂一个分区,满了不会影响数据目录。但核心解法还是优化SQL,让GROUP BY和ORDER BY走索引,不产生磁盘临时表。

第五个吃硬盘大户:慢日志和general_log

慢查询日志开久了就是磁盘炸弹。我那次38G慢日志,是因为long_query_time设了0.1秒,生产环境下80%的查询都被记录进去。这个阈值只在调优期临时用,稳定后要调回1秒。

更危险的是general_log,它记录MySQL收到的每一条SQL,写入量极大。高并发下它的性能损耗往往比磁盘占用更先暴露,尤其写到表里时。log_output默认是FILE,日志写文件;只有设成TABLE才会进mysql库的general_log和slow_log表,8.0里这些表可能是CSV或MyISAM引擎,查询方便但同样吃盘。

排查方法

-- 查看慢日志状态
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';

-- 查看general_log
SHOW VARIABLES LIKE 'general_log%';

清理方法

# 清空慢日志(不停机)
> /var/log/mysql/slow.log

# 或者轮换日志
mv /var/log/mysql/slow.log /var/log/mysql/slow.log.bak
mysqladmin flush-logs

mysqladmin flush-logs会让MySQL关闭当前日志文件、打开新的,这样就不用停服务。

治本方案

# my.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
# 生产建议1秒,调优期可设0.5秒
long_query_time = 1
# 关闭general_log,只在排错时临时开
general_log = 0
# 未走索引的查询也记录,排错时临时开
log_queries_not_using_indexes = 0

用logrotate做日志轮换。

# /etc/logrotate.d/mysql-slow
/var/log/mysql/slow.log {
   
    daily
    rotate 7
    compress
    missingok
    postrotate
        mysqladmin flush-logs
    endscript
}

一套完整的磁盘监控方案

事后我搭了这套监控。Prometheus通过mysqld_exporter采集MySQL指标,Grafana做可视化。关键告警规则:磁盘使用率超80%预警,超90%紧急告警,binlog文件数量超30个预警,单个binlog超2G预警,慢日志超5G预警,ibdata1超50G预警。这些规则看着简单,但能挡掉90%的磁盘事故。

除了磁盘占用,还要盯两个容易被忽略的指标。一个是history list length,反映purge是否滞后;另一个是Created_tmp_disk_tables和Created_tmp_tables的比值,反映临时表落盘比例。后者长期超5%,就该去翻慢日志优化SQL了。

避坑清单

ibdata1共享表空间一旦撑大就缩不回来,想回收只能全量导出、重建库、再导入,全程停机。所以我一直强调innodb_file_per_table必须从第一天就开,让每张表独立成.ibd文件,单表碎片还能用OPTIMIZE TABLE单独回收。老系统如果还是OFF,趁数据量没大到不可收拾之前改掉,别拖。

清理binlog最忌讳用rm直接删。binlog index文件里记录着所有文件名,rm删了文件但index不更新,下次启动MySQL直接报错起不来。要用PURGE BINARY LOGS,它会同步更新index。主从架构下更要先确认从库追平再清,否则从库要拉的binlog被删,主从就断了。

OPTIMIZE TABLE本质是重建表,期间会锁表,而且重建需要额外的临时空间。大表直接OPTIMIZE,反而可能把本就不多的磁盘写爆。大表要么在业务低峰期做,要么用gh-ost、pt-online-schema-change在线重建,边导数据边切表,业务几乎无感。

general_log是个隐藏炸弹,它记录每一条SQL,高并发下性能损耗往往比磁盘占用更先暴露。只在排错时临时开,用完立刻关。慢日志的long_query_time也别长期设0.1秒这种激进值,调优期用完就调回1秒,否则慢日志自己就是下一个磁盘大户。

总结

回头看这次事故,磁盘爆盘从来不是某一个文件的锅,而是缺了对"谁在吃盘"的持续关注。五个大户其实可以分成两类:binlog、undo、临时表是流量型,跟着写入量走,靠配置和SQL优化就能控住;ibdata1和慢日志是存量型,一旦撑大难回收,只能靠预防。真正要记住的也就三件事:innodb_file_per_table从第一天开,清理用PURGE别用rm,general_log用完就关。剩下的交给监控。

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

相关文章
|
9天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1869 119
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
|
10天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1428 13
|
15天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1964 10
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
7天前
|
编解码 弹性计算 云计算
MiniMax-H3 视频生成模型 — 一键部署与使用指南
MiniMax-H3是MiniMax开源的33B全模态视频生成模型,支持文生视频、图生视频、参考生视频三种模式,原生输出2K/15秒带立体声音频视频,已原生适配ComfyUI,并可通过阿里云计算巢一键部署。(239字)
|
10天前
|
人工智能 JSON Shell
2026AI漫剧本地全开源方案(附各个软件模型链接),8G显卡也能流畅运行
这是一套完全本地化部署的AI漫剧生成技术链路:涵盖LLM剧本分镜生成、FLUX文生图(IP-Adapter人脸锁定)、StoryDiffusion时序连贯控制、LTX-2.3唇形同步视频生成,及ComfyUI全流程调度。零云端费用,仅耗硬件算力,单集2–4小时可产出竖屏短视频,适配抖音/B站分发。
|
8天前
|
人工智能 API 开发工具
2026 零基础本地 AI 漫剧完整实操教程(8G 笔记本显卡可用|附可直接复制命令与代码)
本方案提供完全离线、本地运行的漫剧全自动制作流程:RTX3060/4050 8G显卡即可驱动,涵盖Qwen写分镜→ComfyUI统一角色绘图→LTX2.3图生微动画→Qwen3-TTS本地配音→FFmpeg自动合成,全程无水印、免API、不限次。专为低显存优化,解决变脸、闪烁、爆内存三大痛点。(239字)
|
22天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
3358 5
|
9天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
555 113