数据库基础体系 · 第 78/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
数据库容量规划:工作集、缓存命中、IOPS、连接、增长和压测
数据库容量规划不是简单地把“当前数据量”乘以一个安全系数。数据库能否满足 SLO,取决于一组相互制约的量:
- 请求在一个时间窗口内访问哪些数据,即工作集;
- 内存缓存能否保留这些数据,即缓存命中;
- 未命中的访问如何转化为存储读写,即 IOPS、吞吐和延迟;
- 同时有多少请求、事务和连接,即并发与连接;
- 数据、索引、WAL 或 binlog、临时文件和副本如何增长;
- 在真实数据分布和事务语义下,系统何时进入排队、抖动或故障。
容量规划的目标不是预测一个永远正确的数字,而是建立从业务请求到数据库资源的可验证模型,并通过监控和压测不断修正它。
一、先定义容量:不是“能存多少”,而是“在 SLO 下能承受多少”
数据库容量至少包含三个不同问题。
1. 存储容量
存储容量回答:
数据库能否保存未来一段时间的数据、索引、日志、临时文件和运维余量?
它主要受以下对象影响:
- 表数据;
- 二级索引、主键索引和内部元数据;
- PostgreSQL 的 WAL,MySQL InnoDB 的 redo log、undo 相关空间以及 binlog;
- 排序、哈希、临时表产生的临时文件或临时表空间;
- MVCC 旧版本、死元组或尚未 purge 的 undo;
- 备份、归档和复制保留所需的空间。
2. 性能容量
性能容量回答:
在指定读写比例、请求大小、并发量和 SLO 下,数据库每秒能处理多少请求或事务?
性能瓶颈可能来自:
- CPU;
- 内存带宽;
- 数据库缓存;
- 存储 IOPS;
- 存储带宽和单次 IO 延迟;
- 锁、事务冲突和日志刷盘;
- 连接管理;
- 复制延迟;
- SQL 执行计划。
3. 故障与增长容量
故障和增长容量回答:
发生流量峰值、缓存冷启动、主从切换、批量任务或磁盘故障时,系统是否仍有恢复空间?
因此,容量规划的基本约束可以写成:
其中系统容量不是单一数字,而是 CPU、内存、IOPS、连接数、锁等待、复制延迟和存储空间共同决定的最小值。
例如,存储还能提供 20 万 IOPS,并不意味着数据库能承受 20 万次请求每秒。每个请求可能执行多次索引访问、数据页访问和 WAL 写入;同时 CPU、锁和连接池也可能更早成为瓶颈。
二、工作集:决定缓存是否有意义的访问集合
2.1 工作集不是数据库总大小
**工作集(working set)**是某个时间窗口内,业务请求高概率访问的数据页、索引页和相关元数据的集合。
设数据库中的页集合为 ,在观察窗口 内访问页 的次数为 ,则可以按访问次数或访问概率定义工作集。例如,以访问频率阈值 定义:
这个定义表达了两个重要事实:
- 工作集与时间窗口有关;
- 工作集是访问模式的属性,不是表大小的别名。
一个 10 TB 的历史订单库,最近 30 天可能只有 200 GB 被频繁访问;但如果业务每天运行一次全表报表,报表执行期间的工作集又会暂时接近整个表和索引。
2.2 工作集通常分层
实际系统经常同时存在多个工作集:
- 热点工作集:例如最近几分钟的订单、库存和会话;
- 业务工作集:例如最近 30 天的订单;
- 批处理工作集:夜间报表或对账涉及的大范围数据;
- 索引工作集:查询可能只访问索引页,不立即访问表数据页;
- 写入工作集:新增记录对应的数据页、索引页和日志缓冲。
因此,“内存大于某张表”并不自动意味着查询都会命中缓存。查询可能访问的是多个索引、随机数据页和临时结果;反过来,内存小于整张表,也可能因为热点高度集中而获得很高的命中率。
2.3 一个完整的工作集例子
假设某订单查询执行以下访问:
- 按
user_id查二级索引; - 通过索引定位订单行;
- 回表读取订单页;
- 读取订单明细;
- 写入访问日志。
在一个时间窗口内,逻辑页访问统计如下:
| 页类别 | 逻辑访问次数/秒 | 说明 |
|---|---|---|
| 用户订单索引 | 40,000 | 热点用户反复访问 |
| 订单数据页 | 30,000 | 随机回表 |
| 订单明细索引 | 20,000 | 查询明细 |
| 订单明细数据页 | 10,000 | 访问频率较低 |
| WAL/redo 相关写入 | 另计 | 不等于数据页读取 |
如果缓存能保留前两类热点页,它可能已经消除大部分物理读;如果缓存只能保留索引,却无法保留订单数据页,索引命中率看起来很高,查询延迟仍可能很差。
这也是“索引命中率高”不能直接推出“查询性能好”的原因。
三、缓存命中:逻辑访问如何减少为物理访问
3.1 逻辑读与物理读
数据库执行器产生的是逻辑页访问:它需要某个数据页或索引页。
如果该页已经在数据库缓存或可用的操作系统页缓存中,系统可以直接使用;否则需要从存储设备读取,这才形成物理读。
设:
- :单位时间逻辑读次数;
- :缓存命中率;
- :缓存未命中率;
- :单位时间物理读次数。
理想化情况下:
例如:
则:
如果命中率从 95% 提升到 99%,物理读变为:
物理读减少了 80%,不是“命中率只提高了 4 个百分点”那么简单。
3.2 命中率的统计口径必须明确
不同引擎、不同指标的统计范围可能不同:
- 数据库缓存在共享内存中的命中;
- 操作系统页缓存命中;
- 存储设备控制器缓存命中;
- 某个实例、数据库、表或索引的命中;
- 累计统计还是某个时间窗口内的统计。
以 PostgreSQL 为例,pg_stat_database 中的 blks_hit 和 blks_read 可用于观察数据库统计口径下的块命中和读取:
SELECT
datname,
blks_hit,
blks_read,
CASE
WHEN blks_hit + blks_read = 0 THEN NULL
ELSE blks_hit::numeric / (blks_hit + blks_read)
END AS buffer_hit_ratio
FROM pg_stat_database
WHERE datname = current_database();
这个比值是:
它是累计值。若系统刚启动、统计刚重置,或业务模式刚改变,结果不能代表稳定状态。更重要的是,命中率高只能说明很多块访问不需要从更低层次读取,不能证明执行计划合理、锁等待低或延迟满足 SLO。
MySQL InnoDB 可以观察缓冲池请求与物理读请求。例如:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
通常可关注:
Innodb_buffer_pool_read_requests:对 InnoDB 缓冲池的逻辑读请求;Innodb_buffer_pool_reads:无法从缓冲池满足、需要进一步读取的请求。
在近似且相同统计窗口下,可以使用:
必须使用增量而不是直接比较累计总数,否则无法知道当前业务窗口的实际变化。
3.3 数据库缓存与操作系统缓存不能重复计算
常见架构中可能存在两层甚至更多层缓存:
查询
-> 数据库缓冲区
-> 操作系统页缓存(取决于引擎和文件打开方式)
-> 存储设备缓存
-> 物理介质
因此:
- 数据库报告的“未命中”不一定等于物理盘真的完成了一次介质读取;
- 存储设备报告的 IOPS 可能高于数据库看到的请求数,因为存在预读、写放大或内部合并;
- 把数据库缓存、OS 缓存和存储缓存容量简单相加,不能得到有效缓存容量;
- 容量规划应分别观察数据库层、操作系统层和存储层指标。
3.4 缓存命中率高仍然可能很慢
以下查询可能命中率很高,但延迟仍然很差:
SELECT *
FROM orders
WHERE status = 'pending'
ORDER BY created_at DESC;
如果 status='pending' 的记录很多,数据库即使从缓存中读取大量页面,也可能需要:
- 扫描大量索引项或表页;
- 排序;
- 产生大量结果;
- 持有锁或等待其他事务。
反例是:一个查询只返回一行,但执行计划错误地扫描了数百万个缓存中的页面。它的物理读很少,CPU 和执行时间却很高。
所以缓存命中率是资源指标,不是端到端性能指标。必须与执行计划、逻辑读量、CPU 时间、锁等待和请求延迟一起解释。
四、从逻辑读推导 IOPS、吞吐和延迟
4.1 IOPS 的含义
**IOPS(Input/Output Operations Per Second)**是每秒完成的存储 IO 操作数。它不是带宽,也不是数据库请求数。
三者区别如下:
- IOPS:每秒多少次 IO;
- 吞吐:每秒传输多少字节;
- 延迟:一次 IO 完成需要多长时间。
如果每次 IO 大小为 字节,IOPS 为 ,理想吞吐约为:
例如,20,000 IOPS、每次 16 KiB,理论传输量为:
但真实结果还受读写比例、队列深度、随机性、存储并发模型和延迟上限影响。
4.2 一个从请求到 IOPS 的算例
假设某接口每秒接收 2,000 个请求,每个请求平均产生:
- 6 次逻辑索引页访问;
- 4 次逻辑数据页访问;
- 1 次写入相关日志操作。
读逻辑页访问量:
如果读缓存命中率为 97%,估算的读物理访问为:
但这还不是最终存储 IOPS,原因包括:
- 数据库可能预读或合并读取;
- 一次数据库逻辑访问未必对应一次设备 IO;
- 脏页刷盘会额外产生写 IO;
- WAL 或 redo 通常有独立的顺序写和刷盘行为;
- 临时表、排序和后台任务可能产生额外 IO;
- 复制、备份或校验任务可能共享存储。
因此更现实的模型是:
这个模型的价值不在于得到一个精确预测值,而在于避免把“业务 SQL 数量”直接当成“设备 IOPS”。
4.3 排队会放大延迟
设存储设备平均服务时间为 ,到达率为 。当利用率:
接近 1 时,排队等待会快速增加。即使设备名义 IOPS 尚未达到规格上限,延迟也可能已经超过 SLO。
例如,一次 IO 的服务时间从 0.5 ms 增加到 2 ms,在相同请求量下,设备可处理能力近似从:
下降到:
实际设备支持并发 IO,因此不能把这个倒数当作设备规格;这个计算只是说明:单次延迟变化会直接改变服务能力和排队状态。
4.4 写入不能只看数据页写入
事务提交通常还涉及持久化语义:
事务修改
-> 内存中的脏页
-> WAL/redo 记录
-> 日志刷盘或提交确认
-> 后台将脏数据页写回
日志写入与数据页写回不是同一个阶段。数据库可能先持久化日志,再异步写脏页,这能减少随机数据页写入对提交延迟的影响,但不会消除最终的写入压力。
因此,规划写负载时至少要分开估算:
- 业务数据页写入;
- 索引页写入;
- WAL 或 redo 日志量;
- binlog 或归档日志量;
- checkpoint 或刷脏产生的周期性写峰值。
如果只用“每秒写入多少行”估算 IOPS,通常会漏掉索引、日志和后台写入。
五、连接、并发与事务:连接数不是吞吐量
5.1 连接数、活动请求和事务数不同
需要区分:
- 连接数:客户端与数据库建立的会话数量;
- 活动请求数:当前正在执行 SQL 的数量;
- 活动事务数:已开始但尚未提交或回滚的事务数量;
- 锁等待数:正在等待锁或其他资源的请求数量;
- 连接池中的空闲连接:已经建立连接,但当前没有执行请求。
一个系统可以有 1,000 个连接,却只有 20 个活动查询;也可以只有 100 个连接,但每个连接都在执行耗时很长的事务。
PostgreSQL 中可以查看会话状态:
SELECT
state,
count(*)
FROM pg_stat_activity
GROUP BY state
ORDER BY state;
进一步查看长事务:
SELECT
pid,
usename,
application_name,
client_addr,
state,
xact_start,
query_start,
wait_event_type,
wait_event,
query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;
其中,idle in transaction 特别值得关注:它没有执行 SQL,但事务尚未结束,可能阻止旧版本清理、持有锁或扩大复制和维护压力。
MySQL 可使用:
SHOW FULL PROCESSLIST;
或在启用相应 Performance Schema 采集的前提下查询线程、等待和事务信息。Sleep 连接不等于正在消耗同等程度的执行资源,但大量连接仍会消耗会话内存、线程调度和管理开销。
5.2 用 Little 定律估算活动并发
Little 定律为:
其中:
- :系统中平均存在的请求数;
- :稳定状态下的请求到达率;
- :请求平均停留时间。
假设数据库每秒处理 2,000 个请求,平均数据库服务时间为 50 ms:
平均活动请求约为 100,而不是 2,000。
如果由于锁等待,平均停留时间上升到 500 ms:
吞吐没有增加,活动并发却扩大了 10 倍。连接池、线程、内存和队列由此迅速膨胀。
这说明连接数增加不能解决 CPU、IO 或锁瓶颈。连接过多的常见失败路径是:
请求增加
-> 连接池扩大
-> 数据库并发执行增加
-> CPU/IO/锁争用加剧
-> 单请求延迟上升
-> 活动连接继续增加
-> 超时、重试和连接风暴
5.3 连接池大小应由活动并发反推
连接池的目的通常是复用连接、限制数据库并发,而不是让数据库拥有尽可能多的连接。
一个简单估算过程是:
- 根据 SLO 和压测得到平均或高分位数据库服务时间;
- 用 估算稳定状态的活动并发;
- 根据 CPU、IO、锁和事务冲突压测池大小;
- 为突发流量设置有限排队,而不是无限创建连接;
- 为每个实例、每个应用和管理任务预留连接。
例如,目标流量为 2,000 请求/秒,数据库平均服务时间 50 ms,估算活动并发为 100。若压测显示 120 个并发已经使 p95 延迟越过 SLO,则连接池不应盲目设置为 500。额外连接只会在数据库内部排队,并使故障时的恢复更困难。
需要特别注意事务边界:
获取连接
-> 开始事务
-> 执行多条 SQL
-> 提交/回滚
-> 归还连接
连接归还池之前必须结束事务。否则,连接虽然表面上空闲,事务仍可能持有锁或快照。
六、增长规划:不仅预测数据表大小
6.1 数据增长的基本模型
设:
- :每天新增业务记录数;
- :每条记录及其行存储平均占用;
- :索引、页填充和内部开销相对于表数据的比例;
- :每天日志、临时和维护额外空间;
- :规划天数。
粗略估算为:
例如:
- 每天新增 1,000 万行;
- 每行及行级平均数据占用 500 字节;
- 索引和内部开销按 60% 估算;
- 日志及其他额外空间按 80 GiB/天;
- 规划 180 天。
仅表与索引的估算:
约为 7.45 GiB/天,180 天约 1.34 TiB。再加上日志、临时空间、复制保留、备份和运维余量,实际磁盘规划不能只按 1.34 TiB 配置。
该公式只是初始模型。更可靠的方法是从实际数据库中按周或按月观察:
- 表大小;
- 索引大小;
- 日志生成量;
- 临时文件;
- 复制槽或 binlog 保留;
- MVCC 清理滞后;
- 备份和归档保留量。
6.2 空间增长和性能增长不是同一条曲线
数据增长后,可能发生以下变化:
- 索引树高度增加;
- 热点页竞争增加;
- 工作集超过内存;
- 查询选择性下降;
- 统计信息失真;
- 扫描和排序的数据量增加;
- vacuum、purge、checkpoint 或备份持续时间增长;
- 复制延迟增大。
因此,不能只问“磁盘还能用多久”,还要问:
在磁盘耗尽之前,工作集是否已经无法缓存?维护窗口是否已经无法完成?副本是否已经追不上主库?
6.3 空间余量必须覆盖故障路径
磁盘余量的用途不只是接收新数据,还需要覆盖:
- 索引创建期间的临时空间;
- 表重写、在线变更或重建索引;
- 批量更新产生的日志;
- 长事务导致旧版本无法清理;
- 复制中断后的日志保留;
- 失败操作的残留文件;
- 恢复或导入时的临时文件;
- 监控、诊断和备份元数据。
例如,某次索引创建需要额外空间。如果磁盘只剩 5%,即使日常写入尚未超限,DDL 也可能因空间不足失败。容量规划必须把“正常空间”和“操作空间”分开。
七、事务和 MVCC 会改变容量与性能
7.1 长事务为什么会影响空间
MVCC 引擎通常不会立即覆盖所有旧版本,而是保留旧版本,直到确认没有活跃事务需要读取它们。
一个典型路径是:
事务 A 开始
-> 事务 B 更新大量行
-> 旧版本暂时不能清理
-> 表膨胀、undo 或历史版本增加
-> 读放大、维护成本和空间占用上升
在 PostgreSQL 中,长事务、idle in transaction、复制槽保留 WAL 等因素都可能使清理受阻。VACUUM 可以回收可复用空间,但通常不会把普通表占用的空间立即还给操作系统;要缩小文件,往往需要会重写表并带来额外锁定或空间需求的操作,具体取决于操作方式。
在 MySQL InnoDB 中,旧版本由 undo 和 purge 机制管理。长事务可能阻止 purge 前进,使 undo 历史增长。表现可能包括:
- undo 表空间或历史列表增长;
- 读旧版本的成本上升;
- 复制和备份时间变长;
- 磁盘增长速度异常。
因此,增长监控不能只看“插入量”,还要观察事务年龄、清理滞后和日志保留。
7.2 一个反例:低 QPS 也可能耗尽空间
假设业务只有每秒 10 次更新,但一个事务持续 12 小时并读取大量数据。期间其他事务持续更新相同表:
- 新写入量并不高;
- 但旧版本无法及时清理;
- 表或 undo 历史持续增长;
- 查询和维护逐渐变慢。
这说明“QPS 低”不等于“资源压力低”,事务持续时间和快照范围同样是容量变量。
八、如何建立可验证的容量模型
一个实用的模型应从业务请求开始,而不是从数据库总表大小开始。
8.1 第一步:定义请求单位和 SLO
明确:
- 请求是 HTTP 接口、SQL、事务还是批处理任务;
- 读写比例;
- 每个请求包含多少 SQL;
- 每条 SQL 的事务边界;
- 目标吞吐;
- p95、p99 延迟;
- 可接受错误率;
- 高峰持续时间;
- 是否允许异步化、降级或排队。
例如,“每秒 5,000 次请求”没有足够信息。必须知道每次请求是一次简单主键读取,还是包含多次随机读、更新、日志提交和跨表锁竞争的事务。
8.2 第二步:测量每种请求的逻辑资源消耗
对每类 SQL 记录:
- 逻辑读;
- 物理读;
- CPU 时间;
- 执行时间;
- 返回行数;
- 临时空间;
- WAL/redo/binlog 增量;
- 锁等待;
- 事务持续时间。
PostgreSQL 可以在测试环境使用:
EXPLAIN (ANALYZE, BUFFERS)
SELECT *
FROM orders
WHERE user_id = 10001
ORDER BY created_at DESC
LIMIT 20;
其中:
ANALYZE会实际执行查询,因此只应对安全的测试查询使用;BUFFERS用于显示缓冲区访问情况;- 执行计划中的行数估算与实际行数差异,可能揭示统计信息或条件选择性问题。
生产环境直接对高频语句使用 EXPLAIN ANALYZE 需要谨慎,因为它会真正执行语句,写操作尤其不能随意执行。对于只读查询,也要考虑额外执行开销。
MySQL 8.4 可使用普通 EXPLAIN 查看优化器计划;EXPLAIN ANALYZE 会执行语句并提供实际执行信息,适合在受控环境使用。不同语句类型和版本支持范围应以对应版本手册为准,不能把测试命令直接用于生产写操作。
8.3 第三步:将请求模型换算为资源需求
设第 类请求的到达率为 ,每个请求的平均物理读量为 ,则总读 IO 近似为:
写入、日志和临时 IO 可以用相同方法分别求和:
活动并发则为:
其中 是第 类请求在数据库内的平均停留时间。需要用 p95 或峰值窗口重新计算,因为平均值会掩盖突发排队。
8.4 第四步:寻找最先饱和的资源
将估算结果与测试结果结合,检查:
- CPU 是否达到平台可持续范围;
- 数据库缓存是否出现持续抖动;
- 物理读和写入是否导致存储延迟上升;
- WAL、redo 或 binlog 刷盘是否进入等待;
- 活动连接是否达到池或数据库上限;
- 锁等待和事务年龄是否持续增长;
- 副本延迟是否超过业务容忍范围;
- 磁盘空间和维护窗口是否仍然足够。
真正的性能容量通常由最早越过 SLO 的资源决定,而不是某个规格表上的最大值。
九、压测:验证模型,而不是制造一个漂亮的 QPS
9.1 压测数据必须接近生产
压测至少应匹配:
- 数据量级;
- 热点分布;
- 主键和索引基数;
- 读写比例;
- 事务大小;
- 提交频率;
- SQL 参数分布;
- 并发连接模型;
- 复制和后台任务;
- 缓存冷启动或预热状态。
用 1 万行均匀随机数据测试一个拥有数十亿行、热点高度集中的生产系统,得到的 QPS 没有代表性。
9.2 冷缓存和热缓存必须分别测试
至少准备两种场景:
热缓存测试
先运行足够长时间,使热点工作集稳定进入缓存,然后观察:
- 稳态 p50、p95、p99;
- 数据库缓存命中;
- 存储读写延迟;
- CPU 和锁等待;
- 日志写入。
冷缓存测试
重启实例、清空测试环境缓存或使用新的数据范围,观察:
- 缓存预热时间;
- 启动后的物理读峰值;
- 请求延迟尾部;
- 是否出现连接超时和重试;
- 预热是否影响线上流量。
冷缓存表现特别重要,因为故障恢复、主备切换和扩容后的新节点都可能经历冷启动。
9.3 压测负载应逐级增加
不要只测一个并发值。可以按以下阶段进行:
基线负载
-> 目标平均负载
-> 目标峰值
-> 峰值持续一段时间
-> 超过峰值的过载
-> 降载观察恢复
每一阶段都记录:
- 吞吐是否仍在增长;
- p95、p99 是否突然上升;
- 活动连接是否线性增长;
- 存储延迟是否先于 IOPS 达到瓶颈;
- 锁等待是否积累;
- 复制延迟是否能恢复;
- 降载后队列、事务和日志是否回落。
如果继续增加并发后吞吐不再增加、延迟却迅速上升,系统已经进入排队区。此时再把连接池扩大,通常只能把排队从应用层转移到数据库层。
9.4 必须压测失败和恢复路径
容量评估不能只测正常状态,还应在受控环境测试:
- 主库重启或故障转移;
- 副本延迟;
- 存储延迟暂时升高;
- 连接池全部失效后重建;
- 日志保留增加;
- 长事务或批量任务并发运行;
- 备份与在线业务同时运行;
- 缓存冷启动;
- 单节点恢复期间的流量转移。
例如,主库切换后,新主库可能缓存未预热,且连接池会同时重建大量连接。稳态压测未必能暴露这一瞬间的连接风暴和物理读峰值。
十、压测结果如何判断:不要只看最大 QPS
一个可用的压测结论至少应包含以下关系:
在数据集规模 X、读写比例 Y、缓存状态 Z、
并发连接 C、事务模型 T 下,
吞吐为 Q,
p95 延迟为 P95,
p99 延迟为 P99,
错误率为 E,
CPU/IOPS/存储延迟/锁等待/复制延迟为……
例如,下面两个结果不能简单比较:
- 测试 A:热缓存、单行主键读取、1,000 个连接;
- 测试 B:冷缓存、包含更新和提交、100 个连接。
连接数更高的 A 不一定更难;请求模型和缓存状态才决定资源消耗。
应重点寻找“拐点”:
- 吞吐在增加并发时仍然增长;
- 延迟保持在 SLO 内;
- 错误率稳定;
- 资源利用率没有持续排队;
- 降载后系统能恢复到原来的稳态。
如果吞吐不再增长而 p99 延迟持续上升,说明新增并发主要变成等待。这个点通常比“压到数据库报错时的最大 QPS”更适合作为容量边界。
十一、常见误区与诊断路径
误区一:数据库总大小就是工作集
失败表现:增加内存后,某些查询变快,但报表仍然慢;或者热点查询已很快,继续增加内存收益很小。
诊断方法:
- 按时间窗口观察表和索引访问频率;
- 区分热点查询、全表扫描和批处理;
- 查看执行计划的逻辑读和实际行数;
- 比较热缓存与冷缓存结果。
误区二:缓存命中率高就说明数据库健康
失败表现:命中率 99%,p99 仍然超时。
可能原因:
- 执行计划扫描了大量缓存页;
- CPU 已饱和;
- 锁等待严重;
- 事务提交被 WAL/redo 刷盘阻塞;
- 查询返回数据量过大;
- 连接在数据库内部排队。
诊断方法:把命中率与 CPU、逻辑读量、锁等待、事务延迟和端到端 SLO 放在同一个时间窗口中比较。
误区三:IOPS 规格等于数据库可用 IOPS
失败表现:存储标称 IOPS 很高,但数据库写入延迟仍然周期性升高。
可能原因:
- 随机读写与顺序读写性能不同;
- IO 大小与规格测试不同;
- 写缓存策略和持久化语义不同;
- checkpoint、刷脏、备份与业务争用;
- 存储延迟在高队列深度下恶化。
诊断方法:同时观察数据库层 IO、设备层 IOPS、吞吐、队列深度和 p95/p99 延迟,不只看一个 IOPS 数值。
误区四:把 max_connections 当成容量
max_connections 或类似上限只是允许的会话数量,不是数据库可持续处理的并发量。
失败表现:
- 应用频繁收到连接拒绝;
- 数据库内存突然升高;
- 大量会话处于空闲或等待;
- 连接池重试进一步放大压力。
诊断方法:
- 区分总连接、活动查询、活动事务和等待连接;
- 检查连接池是否在每个应用实例都配置了过大的上限;
- 检查是否有连接泄漏和
idle in transaction; - 通过压测确定数据库可持续的活动并发。
误区五:只按行数预测磁盘增长
失败表现:表数据增长符合预期,但磁盘增长远超预测。
可能原因:
- 索引增长;
- WAL、redo 或 binlog 保留;
- 长事务导致旧版本积累;
- 临时文件;
- 复制中断后的日志堆积;
- 在线 DDL 或重建索引产生临时副本;
- 表和索引膨胀。
诊断方法:分别绘制表、索引、日志、临时空间、复制保留和维护产生的空间曲线,而不是只看数据库总目录大小。
十二、一个可落地的容量规划闭环
容量规划最终应形成持续闭环:
业务请求模型
-> SQL 与事务模型
-> 工作集和逻辑读测量
-> 缓存、IOPS、连接和日志换算
-> 受控压测
-> 找到 SLO 拐点
-> 生产监控校准
-> 重新估算增长和故障余量
生产监控至少应把以下维度关联起来:
- 请求量、读写比例和请求延迟;
- 活动连接、连接等待和连接建立速率;
- 活动事务、长事务和锁等待;
- 逻辑读、物理读、缓存命中;
- CPU、内存和数据库缓存使用;
- IOPS、吞吐、设备延迟和队列深度;
- WAL、redo、binlog 生成与保留;
- 副本延迟;
- 表、索引、临时空间和磁盘剩余;
- 备份、vacuum、purge、checkpoint 等后台任务。
当这些指标同时记录在相同时间窗口内,才能解释因果链:
热点扩大
-> 缓存未命中增加
-> 物理读增加
-> 存储队列变长
-> SQL 延迟增加
-> 活动连接增加
-> 事务持续时间变长
-> 旧版本和日志保留增加
-> 存储空间继续下降
这条链条也说明,数据库容量问题通常不是某一个参数的问题。增加内存可能降低物理读,但不能修复错误的执行计划;增加 IOPS 可能缓解读压力,但不能解决长事务和锁竞争;增加连接上限可能暂时隐藏应用排队,却可能把系统推入更严重的资源争用。
可靠的容量规划,是在明确的事务、缓存、存储和 SLO 边界内,用可重复的测量验证这些关系,并在工作集、流量模型和数据规模发生变化时重新计算。
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:数据库可观测性:连接、事务、锁、执行计划、复制和 SLO
- 下一篇:多租户数据库设计:共享表、独立 Schema、独立库和数据隔离
- 延伸:数据库复制、分片与高可用:一致性、路由、故障转移和扩容
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论