"这台数据库服务器还能撑多久?"、"下次扩容要买多大?"、"磁盘又要满了怎么办?"——这些问题背后其实是同一件事:容量规划。它不像故障排查那样紧急,但做不好会带来两类代价:预估过小,三天两头扩容、业务受影响;预估过大,钱白花在闲置资源上。
很多中小团队的容量规划全靠"磁盘快满了就加一块"这种被动模式,结果要么突然告警手忙脚乱,要么长期浪费。这篇文章给一套实用的估算方法:怎么估数据量的增长、索引到底占多少空间、binlog 和备份要预留多少、QPS/TPS 和实例规格怎么对应、什么时候该扩容、以及历史数据怎么归档。所有数字都给经验量级,并说明是经验值,实际以你的真实数据和压测为准。
一、从"数数据"开始:先量化现状
容量规划的第一步不是猜未来,而是量现状。你需要四个数据:
第一,当前数据量。各库各表的大小。
-- MySQL:查看每个库的数据与索引大小
SELECT table_schema AS db,
ROUND(SUM(data_length)/1024/1024/1024, 2) AS data_gb,
ROUND(SUM(index_length)/1024/1024/1024, 2) AS index_gb,
ROUND(SUM(data_length+index_length)/1024/1024/1024, 2) AS total_gb
FROM information_schema.tables
GROUP BY table_schema
ORDER BY total_gb DESC;
-- 单表明细,找最大的表
SELECT table_schema, table_name,
ROUND(data_length/1024/1024/1024, 2) AS data_gb,
ROUND(index_length/1024/1024/1024, 2) AS index_gb,
table_rows
FROM information_schema.tables
WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys')
ORDER BY (data_length+index_length) DESC
LIMIT 20;
-- PostgreSQL:查看每个库大小
SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database ORDER BY pg_database_size(datname) DESC;
-- 查看最大的表(含索引)
SELECT relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total,
pg_size_pretty(pg_relation_size(relid)) AS table_only,
pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) AS indexes
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC LIMIT 20;
第二,增长速率。取几个时间点的数据量,算出每天/每月增加多少。最可靠的办法是定期打点记录,比如每周记录一次各库大小,画成趋势线。至少要有最近一个月的数据才能看出规律。
第三,业务增长预期。用户量、订单量、日志量的增长计划。这块要和业务方对齐,比如"下季度预计用户翻倍"。
第四,资源使用基线。CPU 使用率、内存使用、磁盘 IO、QPS/TPS 的正常水位和峰值水位。这些决定了还剩多少余量。
二、估算数据量的增长
有了增长速率,就能做外推。基本公式:
未来数据量 = 当前数据量 + 日均增长 × 天数 × 增长系数
增长系数用来体现业务增长。如果业务稳定,系数取 1;如果预期增长,取 1.2 到 2 甚至更高。保守估计宁可偏高,因为存储不足的代价远大于稍有富余。
举例:一张订单表当前 200GB,最近三个月每月增加 20GB,也就是日均约 0.65GB,业务预期明年翻倍。
- 一年自然增长:20GB × 12 = 240GB。
- 考虑业务翻倍(下半年起加速),保守按 1.5 倍系数:240GB × 1.5 = 360GB。
- 明年同一时间的数据量约 200 + 360 = 560GB。
这只是数据本身,还没算索引、binlog、备份和文件系统开销。所以实际需要的磁盘会比 560GB 大不少。下面逐项加。
三、索引占用的估算
索引是常被低估的部分。很多人只算数据大小,忘了索引也要占空间,结果磁盘总是比预期先满。
索引占用经验上可以占到数据大小的 20% 到 50%,视索引数量和字段长度而定:只建主键和少量索引,占比接近 20%;索引多、字段长(比如多个字符串列建复合索引),占比能到 50% 甚至更高。上面 MySQL 的查询里 index_gb 就是真实的索引占用,可以据此算出你自己业务的索引比例。
粗略估算:
索引空间 ≈ 数据空间 × 索引比例(经验值 0.2 ~ 0.5)
沿用上面的例子,560GB 数据,按 30% 的索引比例,索引约 168GB,数据 + 索引约 728GB。
需要注意索引还有额外开销:一是 B+ 树结构本身有填充因子和页分裂,写入过程中索引会比最终大小更占空间(可能临时膨胀到 1.5 倍);二是删除数据后,InnoDB 的表空间不会自动缩小,产生的碎片会让实际占用大于逻辑数据量,长期运行的库尤其明显。
-- MySQL 查看表碎片(Data_free 不为 0 说明有可回收空间)
SELECT table_schema, table_name, table_rows, data_free/1024/1024 AS free_mb
FROM information_schema.tables
WHERE data_free > 0
ORDER BY data_free DESC LIMIT 20;
-- 回收碎片(会锁表,务必评估:MySQL 8.0 部分场景可在线)
-- ALTER TABLE orders ENGINE=InnoDB; -- 重建表,大表慎用,需在低峰执行
风险提示:
ALTER TABLE ... ENGINE=InnoDB或OPTIMIZE TABLE会重建表,在大表上可能锁表很久、消耗大量 IO,甚至短时间需要接近原表大小的额外磁盘空间(用于存放临时新表)。执行前必须确认磁盘余量、选低峰期,并做好备份。空间不足或锁表时间过长都可能引发线上故障。
四、binlog、redo log 与备份的占用
这三块经常被忽略,却是磁盘的"隐藏大户"。
Binlog:主从复制和恢复都依赖它,体积和写入量直接相关。经验上,binlog 的日增量大致是日数据变更量的 1 到 3 倍(ROW 格式下,更新一行记录可能产生前后镜像,放大明显)。binlog 保留时间越长,占用越大:
binlog 空间 ≈ binlog 日增量 × 保留天数
比如日增 binlog 30GB,保留 7 天,就是约 210GB。若为了做延迟恢复保留 14 天,则翻倍到 420GB。这也是为什么 binlog 要单独规划、单独清理。
-- MySQL 查看 binlog 使用情况
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds'; -- 8.0,单位秒
-- 旧版是 expire_logs_days,单位天
SHOW BINARY LOGS;
风险提示:不要用
PURGE BINARY LOGS或直接rm去删 binlog,尤其是从库还在用的日志。删掉从库尚未拉取的 binlog 会导致复制中断;删掉做点恢复需要的日志会让恢复失败。删除前务必确认主从位置和备份策略,用PURGE BINARY LOGS BEFORE '时间'而非手工删除文件。
Redo log / WAL:InnoDB 的 redo log 默认不大(MySQL 8.0.30 之后可动态调整 innodb_redo_log_capacity,常设 1 到 4GB),但 PostgreSQL 的 WAL 在归档开启时可能持续累积。
备份:本机至少要留一份最近备份的空间。逻辑备份(mysqldump)的文本形式往往比数据本身还大(SQL 文本膨胀),物理备份(xtrabackup)接近原数据大小。建议按"数据 + 索引"的实际大小预留至少 1 份本机备份的空间,如果保留多份则成倍增加。异地备份虽然不占本机磁盘,但传输和存储成本要单独算。
临时空间:排序、大查询、DDL 重建、导入导出都会用临时空间,tmpdir 所在分区要留足余量,否则一个 ALTER TABLE 就可能把 /tmp 撑满导致服务异常。
五、磁盘预算公式
把上面各项汇总,给出一个实用的磁盘预算公式:
所需磁盘 = (数据量 + 索引量) × (1 + 碎片与膨胀系数)
+ binlog 空间
+ 备份空间
+ 临时空间
+ 系统与预留
其中碎片与膨胀系数经验取 0.2 到 0.5(长期运行的库取大值);临时空间取数据量的 10% 到 20%(做重建类操作时需要接近原表大小,建议单独挂盘给 tmpdir);系统与预留至少留 15% 到 20%,因为磁盘使用率过高不仅性能下降,还会触发各种保护和告警(比如 Elasticsearch 的水位、MySQL 写满导致服务异常)。
沿用前面的例子,做个完整估算:
| 项目 | 估算 | 说明 |
|---|---|---|
| 数据 | 560GB | 一年后 |
| 索引 | 168GB | 按 30% |
| 小计 | 728GB | 数据 + 索引 |
| 膨胀系数 | +145GB | 按 20% |
| binlog | 210GB | 日增 30GB × 7 天 |
| 本机备份 | 728GB | 预留 1 份 |
| 临时空间 | 100GB | 单独盘更好 |
| 系统与预留 | 200GB | 约 15% |
| 合计 | 约 2111GB | 约需 2.1TB |
所以这台库一年后需要约 2.1TB 可用磁盘。规划时按"能撑到下次扩容"的时间窗预留,比如要求一次配置支撑 18 个月,那就要再往前推一档。
六、QPS/TPS 与实例规格
存储算完了,还要算计算能力。核心是 QPS(每秒查询)和 TPS(每秒事务),以及它们跟 CPU、内存的对应关系。
先把业务的 QPS/TPS 峰值算出来(注意是峰值,不是平均值,扩容要按峰值留余量)。然后对照经验量级:
| 实例规格 | 单机 QPS 量级(简单查询) | 说明 |
|---|---|---|
| 2 核 4G | 数百到上千 | 开发、小型站点 |
| 4 核 8G | 数千 | 中小业务 |
| 8 核 16G | 数千到上万 | 中等业务 |
| 16 核 32G | 上万到数万 | 较大业务 |
| 32 核以上 | 数万以上 | 需要分区/分库分担 |
这些是经验量级,强烈依赖 SQL 复杂度、数据量、命中率和硬件。复杂查询、全表扫描会大幅拉低这个量级;缓存命中率高、索引良好的简单查询则能到上限。必须用压测在自己的数据和 SQL 上验证,不要照搬。
内存方面,MySQL 的 innodb_buffer_pool_size 希望把热数据装下,所以内存容量的目标是让工作集(经常访问的数据+索引)能放进缓冲池。如果缓冲池装不下热数据,磁盘 IO 会成为瓶颈。一个粗略判断:如果缓冲池大小明显小于实际活跃数据量,就该考虑加内存了。
-- MySQL 缓冲池命中率,理想情况应在 99% 以上(经验值)
SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests';
SHOW STATUS LIKE 'Innodb_buffer_pool_reads';
-- 命中率 = 1 - reads / read_requests
冷数据多、热数据少时(比如几十 GB 的表只有几 GB 被频繁访问),缓冲池不必装下全表,装下热数据即可。所以内存规划要基于工作集而非总数据量。
七、扩容触发阈值
光会算还不够,要提前定好"什么时候必须扩容",把被动救火变成主动规划。
建议设置几个触发点:
- 磁盘使用率超过 70% 开始评估,超过 80% 立即准备扩容,超过 90% 是高危状态。留出 20% 以上缓冲,是为了应对突发写入、备份、临时文件。触发值因环境而异,但绝不该等到 95% 才动手。
- 缓冲池命中率持续低于 99%,且 IO 等待上升,考虑加内存。
- CPU 使用率峰值持续超过 70%,或
Threads_running频繁触顶,考虑加 CPU 或优化。 - 平均响应时间较基线明显上升,是最直接的体感指标。
- 单表数据量超过一定规模(如几千万到上亿行,视行大小而定)时,评估是否需要归档或分表。
配合监控打点,把数据量和资源使用率画成趋势图,能提前看到"还有多久到阈值",在到达前安排扩容窗口。
八、历史数据归档
当数据只增不减时,任何容量规划都会被时间击穿。归档是让小库保持健康的关键手段。
常见做法:
冷热分离:把超过一定时间的冷数据(如一年前的订单、半年前的日志)迁移到归档库或对象存储,主库只保留近期热数据。这样主库体积可控,缓冲池能装下热数据,性能稳定。
按时间分区:用分区表(MySQL 的 PARTITION BY RANGE、PostgreSQL 的声明式分区),把数据按月/按天分区。好处是删除旧数据只需 DROP PARTITION(秒级,且不产生大事务和碎片),比 DELETE 快得多,也不会造成主从延迟。
-- MySQL:按时间分区的示意(按月分区)
-- 注意:分区表的主键必须包含分区键
CREATE TABLE orders (
id BIGINT NOT NULL,
created_at DATETIME NOT NULL,
amount DECIMAL(10,2),
PRIMARY KEY (id, created_at),
KEY idx_created (created_at)
) PARTITION BY RANGE (TO_DAYS(created_at)) (
PARTITION p202601 VALUES LESS THAN (TO_DAYS('2026-02-01')),
PARTITION p202602 VALUES LESS THAN (TO_DAYS('2026-03-01')),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
-- 删除旧分区(比 DELETE 几百万行安全高效得多)
ALTER TABLE orders DROP PARTITION p202601;
风险提示:
DROP PARTITION会直接删除该分区内所有数据且不可恢复,且不写 binlog 的逐行记录(对主从来讲是元数据操作,但数据确实没了)。执行前必须确认该分区数据确实不再需要、且有可用备份。分区表的MAXVALUE分区若忘记维护会导致新数据插入失败,要建立定期增加分区的流程。
归档到对象存储:更冷的冷数据可以导出成文件(如 Parquet、压缩 CSV)放到对象存储,成本低、容量近乎无限,需要时再导入分析库。这也是备份异地化的一部分。
归档要有节奏和监控:定期(如每月)执行归档任务,归档后确认主库体积下降、备份时间缩短。
九、把容量规划做成例行工作
容量规划不是一次性的,而是持续过程。建议:
- 每周记录数据量、磁盘使用率、QPS/TPS、缓冲池命中率等关键指标,形成趋势。
- 每月评审一次增长速率和趋势,对照扩容阈值。
- 每季度结合业务规划更新一次容量预测,提前安排扩容预算。
- 每次大版本上线或大促前,做专项容量评估和压测。
把数据沉淀下来,扩容决策就从"拍脑袋"变成"看数据"。
常见问题
Q:索引真的会占那么多空间吗?
视情况。经验上占数据的 20% 到 50%。索引多、字段长占比高。用 information_schema.tables 的 index_length 能查到真实值,据此算你业务的索引比例。
Q:为什么数据删了,磁盘没变小?
InnoDB 删除数据只是标记可复用,表空间不会自动缩小,会留下碎片。需要重建表(OPTIMIZE TABLE 或 ALTER TABLE ... ENGINE=InnoDB)才能回收,但大表操作有风险,要评估后低峰执行。
Q:binlog 要保留多久合适?
取决于恢复需求。一般 7 天是常见值,需要做延迟恢复或从库较多时保留更久。保留越久占盘越多,要平衡恢复能力和磁盘成本。
Q:备份要占多少空间?
按"数据 + 索引"的实际大小预留至少一份,若保留多份则叠加。逻辑备份文本可能比物理备份更大。异地备份不占本机空间但要算传输和存储成本。
Q:什么时候该分库分表?
单表数据量大到影响查询和维护(常见参考是几千万到上亿行,视行大小而定),且索引优化已经无法解决时。先考虑归档和分区,分库分表复杂度高,是最后手段。
Q:扩容选纵向(升配)还是横向(加节点)?
小规模优先纵向升配,简单、无需改架构。到达单机上限或需要高可用时转向横向(读写分离、分库分表、集群)。
总结
- 容量规划先量化现状(数据量、增长速率、业务预期、资源基线),再做外推,别凭感觉。
- 磁盘预算要算全:数据 + 索引(经验占数据 20%~50%)+ 膨胀碎片 + binlog + 备份 + 临时空间 + 系统预留,缺一项就会提前爆盘。
- QPS/TPS 与实例规格大致对应,但必须以压测和真实工作集为准;内存规划的目标是把热数据放进缓冲池。
- 提前设好扩容阈值(磁盘 70%/80%/90%、缓冲池命中率、CPU 峰值、响应时间),把被动救火变成主动规划。
- 归档和按时间分区是控制数据体积的关键,
DROP PARTITION比DELETE安全高效,但不可恢复,务必确认备份。 - 把容量规划变成每周打点、每月评审、每季预测的例行工作,扩容决策靠数据而不是拍脑袋。
企业QQ咨询




