TouchAll技术博客

TouchAll技术博客

[系统设计 - 数据库设计专题] 2.数据库容量规划

2026-07-19

做数据库容量规划的核心目的,是精准评估当前存储基线、通过预估数据增长速率、预留未来 3–5 年业务容量上限,通过预留合理冗余余量、规范单表 / 索引容量边界、设计分层存储与平滑扩容架构,从根源避免业务突发增量、数据持续膨胀导致的磁盘打满、数据库性能雪崩、架构无力承载业务迭代的问题,保障系统长期稳定、可预判、可演进。

容量现状分析

如果已有现实运行项目

统计现有数据库的数据量(表大小、索引、日志等)、存储占用、QPS/TPS(请求量)、连接数、CPU/内存使用率等指标。

根据业务口径估算

核心思路:技术指标(QPS/存储)是结果,业务指标(用户量/订单量)才是源头。容量规划必须从业务口径出发,倒推技术需求。

估算方法论

业务指标 → 数据模型 → 存储估算 → 性能估算
   ↑
  产品/运营提供

关键业务指标清单

业务维度 核心指标 数据来源
用户规模 DAU、MAU、注册用户总数 产品/运营
交易规模 日订单量、月 GMV、客单价 业务方
内容规模 日新增帖子/评论/图片数 内容运营
增长预期 未来 1~3 年用户/交易增长倍数 CEO/业务负责人

估算公式

日新增行数 = 日活跃用户数 × 每用户日均操作次数
单行大小   = 字段类型长度之和 + 行头开销(MySQL约9字节)
日增存储   = 日新增行数 × 单行大小 × (1 + 索引膨胀系数)
月增存储   = 日增存储 × 30
年增存储   = 日增存储 × 365

案例:电商平台容量估算

业务背景:某电商平台,当前 MAU 100万,日订单量 5万单,客单价 200元。业务预期未来 3 年用户量增长 5 倍。

Step 1:梳理核心表结构

-- 订单表
orders (
  id BIGINT,           -- 8B
  user_id BIGINT,      -- 8B
  order_no VARCHAR(32),-- 32B
  amount DECIMAL(10,2),-- 5B
  status TINYINT,      -- 1B
  created_at DATETIME, -- 8B
  updated_at DATETIME, -- 8B
  -- 行头开销 ~9B
  -- 合计: ~79B ≈ 80B
)

-- 订单明细表
order_items (
  id BIGINT,           -- 8B
  order_id BIGINT,     -- 8B
  product_id BIGINT,   -- 8B
  quantity INT,        -- 4B
  price DECIMAL(10,2), -- 5B
  -- 合计: ~42B ≈ 50B
)

-- 用户表
users (
  id BIGINT,           -- 8B
  phone VARCHAR(20),   -- 20B
  nickname VARCHAR(50),-- 50B
  status TINYINT,      -- 1B
  created_at DATETIME, -- 8B
  -- 合计: ~96B ≈ 100B
)

Step 2:计算日增量

表 日新增行数 单行大小 日增存储 索引膨胀(+40%)
orders 5万 80B 4MB 5.6MB
order_items 15万(平均3件/单) 50B 7.5MB 10.5MB
users 1000(日新增注册) 100B 0.1MB 0.14MB
合计 - - 11.6MB 16.24MB

Step 3:推算未来容量

当前数据量:已运行 2 年,约 100GB
日增量:16.24MB/天 ≈ 0.5GB/月
年增量:约 6GB/年

未来 3 年(用户增长 5 倍):
- 第 1 年:日增 16MB,年增 6GB,累计 106GB
- 第 2 年:日增 40MB(用户 2x),年增 15GB,累计 121GB
- 第 3 年:日增 80MB(用户 5x),年增 30GB,累计 151GB

3 年后预估总数据量:150GB ~ 200GB(含索引)

Step 4:性能指标估算

当前 QPS:
- 读:200 QPS(订单查询、用户查询)
- 写:50 TPS(下单、更新状态)

未来 3 年(5 倍增长):
- 读:1000 QPS
- 写:250 TPS

峰值系数(大促 3 倍):
- 读峰值:3000 QPS
- 写峰值:750 TPS

资源需求计算

存储资源计算

总存储需求 = 数据量 + 索引量 + 日志量 + 备份量 + 冗余余量

数据量     = 预估总行数 × 单行大小
索引量     = 数据量 × 索引膨胀系数(0.3~0.5)
日志量     = Binlog/WAL 日增量 × 保留天数
备份量     = 全量备份大小 × 备份份数 + 增量备份
冗余余量   = (数据+索引+日志) × 20%~30%

案例续:电商平台资源计算

存储资源:

项目 计算 大小
数据量 3 年后 150GB 150GB
索引量 150GB × 40% 60GB
Binlog/WAL 日志 5GB/天 × 30天 35GB
全量备份 200GB × 2份(本地+异地) 400GB
增量备份 5GB/天 × 30天 150GB
小计 - 795GB
冗余 30% 795GB × 30% 240GB
总计 - ~1TB

计算资源:

CPU 需求:
- 基准:每 1000 QPS 需要 2 核(经验值)
- 峰值 3000 QPS → 6 核
- 预留 30% → 8 核

内存需求:
- 热数据缓存:150GB × 20%(活跃数据比例)= 30GB
- Buffer Pool:热数据 × 1.5 = 45GB
- 连接内存:500连接 × 10MB = 5GB
- 系统预留:8GB
- 总计:~60GB → 选 64GB

磁盘 IOPS:
- 峰值写:750 TPS × 每次写 10 次 IO = 7500 IOPS
- 峰值读:3000 QPS × 缓存命中率 80% → 600 次实际读 × 5 IO = 3000 IOPS
- 总计:~10000 IOPS
- 选 SSD 云盘(通常 3000~10000 IOPS/TB)

最终配置建议:

资源 规格
CPU 8 核
内存 64GB
磁盘 1TB SSD(IOPS > 10000)
网络 万兆以上

冗余与扩展设计

冗余设计原则

层级 冗余策略 目的
磁盘 预留 30% 空间 应对突发增长、避免磁盘打满
连接 连接池上限 > 峰值 × 1.5 应对流量突增
CPU/内存 峰值时使用率 < 70% 预留处理余量
副本 至少 1 主 2 从 读扩展 + 高可用

扩展方案设计

纵向扩展(Scale Up)— 优先选择

触发条件:单表数据量 < 5000万,磁盘 < 500GB
方案:升级实例规格(CPU/内存/磁盘)
优点:无需改代码,运维简单
缺点:有上限,成本非线性增长

横向扩展(Scale Out)— 分阶段实施

阶段 1:读写分离(单表 < 1亿,读多写少)
┌────────┐
│  应用   │
└────┬───┘
     ↓
┌────────┐
│ Proxy  │ ← 读写路由
└────┬───┘
     ↓
┌────┴────┬─────────┐
│  主库    │  从库×2  │
│  (写)   │  (读)    │
└─────────┴─────────┘

阶段 2:分库分表(单表 > 5000万 或 磁盘 > 500GB)
- 按 user_id 取模分 16 库 × 16 表 = 256 张表
- 单表数据量控制在 500万以内
- 引入 ShardingSphere / Vitess 中间件

阶段 3:冷热分离(数据量 > 1TB)
- 热数据(近 3 个月):SSD
- 温数据(3~12 个月):HDD
- 冷数据(> 1 年):对象存储 + 元数据索引

案例续:电商平台扩展路径

当前(100GB):
- 单库单表,8C64G,1TB SSD
- 读写分离:1主1从

第 1 年(120GB):
- 纵向扩展:16C128G,2TB SSD
- 增加从库:1主2从

第 2 年(200GB):
- 引入分库分表:按 user_id 分 4 库
- 单库数据量控制在 50GB 以内

第 3 年(400GB):
- 扩展到 16 库
- 引入冷热分离,历史订单归档到 HDD

预备扩容方案设计

扩容触发条件

指标 预警阈值 扩容阈值 紧急阈值
磁盘使用率 > 70% > 80% > 90%
CPU 使用率 > 60% > 75% > 90%
内存使用率 > 70% > 85% > 95%
单表行数 > 3000万 > 5000万 > 1亿
主从延迟 > 1秒 > 5秒 > 30秒

扩容方案矩阵

场景 扩容方式 操作步骤 停机时间 回滚方案
磁盘不足 云盘在线扩容 控制台扩容 → 文件系统 resize 0(在线) 无需回滚
CPU/内存不足 升级实例规格 创建新实例 → 数据同步 → 切换 分钟级 切回老实例
读压力高 增加从库 搭建从库 → 配置路由 0(在线) 移除从库
写压力高 分库分表 双写 → 数据迁移 → 切读 → 切写 小时级 回切老库
单表过大 分表 + 归档 分表迁移 → 历史归档 小时级 回切老表

案例:磁盘扩容实操

场景:磁盘使用率达到 82%,预计 2 个月后打满

Step 1:评估现状

# 查看磁盘使用
df -h /data/mysql
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/xvdb       500G  410G   90G  82% /data/mysql

# 查看各表大小
SELECT 
  table_name,
  ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb
FROM information_schema.tables
WHERE table_schema = 'ecommerce'
ORDER BY (data_length + index_length) DESC
LIMIT 10;

# 结果:
# orders         | 45000 MB
# order_items    | 32000 MB
# users          | 8000 MB
# products       | 5000 MB

Step 2:快速释放空间

-- 清理 7 天前的 Binlog
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 释放约 35GB

-- 归档 1 年前的订单到历史表
INSERT INTO orders_history SELECT * FROM orders 
WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
DELETE FROM orders WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-- 释放约 20GB

Step 3:在线扩容(云环境)

# AWS RDS
aws rds modify-db-instance \
  --db-instance-identifier ecommerce-prod \
  --allocated-storage 1000  # 从 500GB 扩到 1TB

# 监控扩容进度
aws rds describe-db-instances \
  --db-instance-identifier ecommerce-prod \
  --query 'DBInstances[0].StorageEncrypted'

Step 4:验证与监控

# 确认扩容生效
df -h /data/mysql
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/xvdb       1.0T  355G  645G  36% /data/mysql  ✓

# 配置告警
# 磁盘使用率 > 70% 告警

案例:分库分表扩容

场景:orders 表达到 8000 万行,查询变慢,需要分表

Step 1:选择分片键

分析:
- 90% 查询都带 user_id
- 订单查询都是"我的订单"场景
- 结论:按 user_id 分片

分片策略:
- user_id % 16 = 分片编号(0~15)
- 16 个库,每库 16 张表(orders_00 ~ orders_15)
- 单表数据量控制在 500万以内

Step 2:双写阶段

# 应用层双写逻辑
def create_order(user_id, order_data):
    shard_id = user_id % 16
  
    # 1. 写新库(分片库)
    new_db.insert(f"orders_{shard_id}", order_data)
  
    # 2. 写老库(兜底)
    old_db.insert("orders", order_data)
  
    # 3. 记录迁移日志
    migration_log.insert({
        "order_id": order_data["id"],
        "shard_id": shard_id,
        "status": "dual_write"
    })

Step 3:数据迁移

# 批量迁移历史数据
def migrate_orders():
    offset = 0
    batch_size = 1000
  
    while True:
        # 从老库读取
        orders = old_db.query(
            "SELECT * FROM orders ORDER BY id LIMIT %s OFFSET %s",
            batch_size, offset
        )
    
        if not orders:
            break
        
        # 写入新库
        for order in orders:
            shard_id = order["user_id"] % 16
            new_db.insert(f"orders_{shard_id}", order)
        
        offset += batch_size
        print(f"Migrated {offset} rows")

Step 4:切读验证

# 灰度切读:10% 流量走新库
def get_orders(user_id):
    if hash(user_id) % 100 < 10:  # 10% 灰度
        return new_db.query(f"orders_{user_id % 16}", user_id=user_id)
    else:
        return old_db.query("orders", user_id=user_id)

# 对比验证
def verify_consistency(user_id):
    old_data = old_db.query("orders", user_id=user_id)
    new_data = new_db.query(f"orders_{user_id % 16}", user_id=user_id)
  
    if old_data != new_data:
        alert("Data inconsistency detected!")

Step 5:全量切读 → 切写 → 下线老库

Week 1: 10% 读流量 → 新库
Week 2: 50% 读流量 → 新库
Week 3: 100% 读流量 → 新库,老库只写
Week 4: 100% 写流量 → 新库,老库只读(兜底)
Week 5: 确认无问题,下线老库

预备迁移方案设计

迁移场景分类

场景 触发条件 迁移方式 风险等级
同构迁移 机房迁移、云厂商切换 物理备份 + 增量同步 低
跨构迁移 MySQL → PostgreSQL 数据转换 + 双写验证 高
分片迁移 单库 → 分库分表 分片路由 + 数据拆分 中
版本升级 MySQL 5.7 → 8.0 就地升级 / 迁移到新实例 中

迁移方案模板

1. 迁移目标
   - 源环境:xxx
   - 目标环境:xxx
   - 迁移数据量:xxx
   - 停机窗口:xxx

2. 迁移策略
   - 全量迁移:xxx
   - 增量同步:xxx
   - 数据校验:xxx

3. 回滚方案
   - 回滚触发条件:xxx
   - 回滚步骤:xxx
   - 回滚时间:xxx

4. 迁移步骤
   - Step 1: xxx
   - Step 2: xxx
   ...

5. 验证清单
   - [ ] 数据完整性
   - [ ] 业务功能
   - [ ] 性能指标
   - [ ] 监控告警

案例:MySQL 跨云迁移

场景:从 AWS RDS 迁移到阿里云 RDS,数据量 500GB,要求停机时间 < 30 分钟

Step 1:迁移方案选择

方案 A:停机导出导入
- 停机时间:500GB / 100MB/s = 5000秒 ≈ 83分钟
- 不满足要求 ✗

方案 B:物理备份 + 增量同步
- 全量备份:500GB,耗时 2 小时
- 增量同步:基于 Binlog,实时同步
- 停机切换:分钟级
- 满足要求 ✓

Step 2:迁移步骤

# Day 1:搭建目标环境
# 1. 创建阿里云 RDS 实例(同配置:8C64G,1TB SSD)
# 2. 配置网络:打通 AWS ↔ 阿里云专线

# Day 2:全量备份
# AWS 侧
mysqldump -h aws-rds.xxx.amazonaws.com \
  --single-transaction \
  --master-data=2 \
  --all-databases > full_backup.sql

# 传输到阿里云(500GB,专线 1Gbps,约 1.5 小时)
scp full_backup.sql ali-rds:/tmp/

# Day 3:导入 + 增量同步
# 阿里云侧导入
mysql -h ali-rds.xxx.aliyuncs.com < full_backup.sql

# 配置增量同步(使用 DTS 数据迁移服务)
# 1. 创建 DTS 迁移任务
# 2. 源:AWS RDS,目标:阿里云 RDS
# 3. 同步类型:增量同步(基于 Binlog)
# 4. 启动同步,延迟 < 1秒

# Day 4:数据校验
# 对比行数
SELECT COUNT(*) FROM orders;  # AWS: 8000万,阿里云: 8000万 ✓
SELECT COUNT(*) FROM order_items;  # AWS: 2.4亿,阿里云: 2.4亿 ✓

# 抽样校验
SELECT * FROM orders ORDER BY RAND() LIMIT 100;
# 对比两端数据一致性 ✓

# Day 5:停机切换
# 1. 通知业务方:凌晨 2:00~2:30 停机维护
# 2. 2:00 停止应用写入(只读模式)
# 3. 等待 DTS 同步完成(延迟 = 0)
# 4. 切换 DNS / 连接串到阿里云
# 5. 启动应用,验证读写正常
# 6. 2:15 切换完成,总停机 15 分钟 ✓

# Day 6~12:观察期
# - 监控性能指标
# - 对比 AWS 和阿里云的 QPS/延迟
# - 确认无异常后,下线 AWS RDS

Step 3:回滚方案

回滚触发条件:
- 切换后 1 小时内出现数据不一致
- 性能指标下降 > 50%
- 出现无法快速修复的 Bug

回滚步骤:
1. 停止应用写入
2. 反向配置 DTS(阿里云 → AWS)
3. 等待增量同步完成
4. 切换 DNS 回 AWS
5. 启动应用

回滚时间:约 30 分钟

案例:MySQL → PostgreSQL 跨构迁移

场景:业务从 MySQL 5.7 迁移到 PostgreSQL 14,数据量 100GB

Step 1:差异分析

差异点 MySQL PostgreSQL 处理方式
自增主键 AUTO_INCREMENT SERIAL/IDENTITY DDL 转换
字符串 VARCHAR默认不补空格 同 兼容
日期函数 NOW() NOW() 兼容
分页 LIMIT offset, count LIMIT count OFFSET offset SQL 改写
事务 支持 DDL 回滚 支持 DDL 回滚 PG 更强
JSON JSON类型 JSONB(索引支持) 需改造

Step 2:DDL 转换

-- MySQL 原始 DDL
CREATE TABLE orders (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(32) NOT NULL,
  amount DECIMAL(10,2),
  status TINYINT DEFAULT 0,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_user_id (user_id),
  UNIQUE INDEX uk_order_no (order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- PostgreSQL 转换后
CREATE TABLE orders (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(32) NOT NULL,
  amount NUMERIC(10,2),
  status SMALLINT DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE UNIQUE INDEX uk_orders_order_no ON orders(order_no);

Step 3:数据迁移

# 使用 pgloader 工具(支持 MySQL → PostgreSQL 自动转换)
pgloader mysql://user:pass@mysql-host/ecommerce \
  postgresql://user:pass@pg-host/ecommerce

# 或使用 AWS DMS / 阿里云 DTS(支持异构迁移)

Step 4:应用改造

# 分页查询改造
# MySQL
cursor.execute("SELECT * FROM orders LIMIT %s, %s", (offset, limit))

# PostgreSQL
cursor.execute("SELECT * FROM orders LIMIT %s OFFSET %s", (limit, offset))

# JSON 查询改造
# MySQL
SELECT data->>'$.name' FROM users WHERE id = 1;

# PostgreSQL
SELECT data->>'name' FROM users WHERE id = 1;

Step 5:双写验证

# 应用层双写,对比一致性
def create_order(order_data):
    # 写 MySQL
    mysql_id = mysql_db.insert("orders", order_data)
  
    # 写 PostgreSQL
    pg_id = pg_db.insert("orders", order_data)
  
    # 对比
    mysql_row = mysql_db.query("orders", mysql_id)
    pg_row = pg_db.query("orders", pg_id)
  
    if mysql_row != pg_row:
        log_error("Data inconsistency", mysql_row, pg_row)

总结

数据库容量规划的核心交付物:

交付物 内容
容量评估报告 当前数据量、增长速率、3 年预测
资源配置清单 CPU/内存/磁盘/IOPS 规格
扩展路径图 纵向扩展 → 读写分离 → 分库分表 → 冷热分离
扩容 SOP 触发条件、操作步骤、回滚方案
迁移预案 同构/异构迁移方案、停机时间、回滚策略