3. 系统资源监控

PostgreSQL 的性能问题很少直接以“数据库慢”这种形式暴露出来。它通常先表现为某个系统资源被持续压榨:CPU 的 sys 占比异常升高、内存出现频繁换页、磁盘 await 飙升、网络重传增多。要定位这类问题,必须把操作系统层面的资源指标与数据库内部行为对应起来。本节围绕 CPU、内存、磁盘 I/O、网络四类资源,说明它们在 PostgreSQL 负载下的典型表现,以及如何用 top、iostat、vmstat、sar 等工具把系统指标翻译成数据库层面的判断。

CPU:区分用户态、内核态与等待

在 top 中,PostgreSQL 的 CPU 消耗主要落在用户态(us)和内核态(sy)。纯查询计算、排序、聚合、哈希连接属于用户态消耗;而大量短连接、频繁 fork 新进程、系统调用密集的操作会推高 sy。如果 sy 明显偏高,往往不是 SQL 本身慢,而是连接管理或系统调用开销过大,此时应检查连接池配置、是否频繁建立新连接。

另一个关键指标是 wa(I/O 等待)。当 wa 持续超过 10%~20%,说明 CPU 在等磁盘,瓶颈不在计算而在存储。此时即使 CPU 空闲率高,数据库响应也会变慢。用 vmstat 1 可以进一步观察 r(运行队列)和 b(阻塞进程数):r 长期大于 CPU 核数说明 CPU 排队,b 持续大于 0 说明有进程卡在 I/O 上。

# 观察整体 CPU 与运行队列
top -b -n 1 | head -20
vmstat 1 5
# 按 CPU 核查看,判断是否单核瓶颈
mpstat -P ALL 1 3

PostgreSQL 是进程模型,每个连接对应一个后端进程,因此单条复杂查询只能跑在一个 CPU 核上。如果发现某个核 100% 而其他核空闲,通常是单条大查询或并行度不足导致的单核瓶颈,而不是整体 CPU 不足。

内存:共享缓冲区、工作内存与换页

PostgreSQL 的内存分为共享内存和进程私有内存。共享内存中最重要的是 shared_buffers,它缓存数据页;每个排序、哈希操作还会使用 work_mem 指定的私有内存。当 work_mem 设置过大且并发较高时,总内存需求会迅速膨胀,可能触发操作系统换页。

用 vmstat 观察 si(换入)和 so(换出)。只要 si/so 持续非零,就说明物理内存不足,数据库正在被换页拖慢。此时 free -m 中的 available 比 free 更有参考价值,因为 Linux 会把空闲内存用于文件缓存。如果 available 很低且 si/so 活跃,应优先降低 work_mem 或减少并发,而不是盲目扩大 shared_buffers。

判断内存是否真正紧张,看 vmstat 的 si/so 是否持续大于 0,而不是看 free 剩余多少。文件缓存被回收是可接受的,换页才是问题。

磁盘 I/O:await、利用率与 PostgreSQL 的访问模式

PostgreSQL 的磁盘访问主要有三类:查询读取数据页(随机读)、检查点刷脏页(顺序或随机写)、WAL 顺序写。用 iostat -x 1 关注 %util、await、r/s、w/s 和 aqu-sz。%util 接近 100% 说明设备饱和;await 明显高于正常基线说明请求排队。

如果 w/s 和 await 在检查点期间周期性飙升,通常是 checkpoint_completion_target 过低或 max_wal_size 太小,导致检查点集中刷盘。如果随机读的 await 高而 %util 不高,可能是存储本身延迟高或队列深度不足。WAL 写入应尽量落在独立设备上,避免与数据文件争抢 I/O。

# 查看设备级 I/O 指标
iostat -x 1 5
# 查看 PostgreSQL 检查点与 WAL 情况
SELECT checkpoints_timed, checkpoints_req, 
       checkpoint_write_time, buffers_checkpoint 
FROM pg_stat_bgwriter;

网络:连接数、吞吐与重传

网络在 PostgreSQL 中主要影响客户端与服务器之间的数据传输。大批量 SELECT 返回大量行、逻辑复制、流复制都会占用带宽。用 sar -n DEV 1 观察网卡吞吐,用 sar -n EDEV 1 观察错误和丢包,用 ss -s 查看连接状态。如果 retrans 或 drop 持续增长,说明网络质量有问题,会表现为查询结果返回慢但数据库内部执行很快。

此外,连接数过多会消耗内存和文件描述符,并增加上下文切换。用 ss -tn state established '( sport = :5432 )' | wc -l 可以统计当前到 PostgreSQL 的连接数,与 max_connections 对比,判断是否需要引入连接池。

PostgreSQL 四类系统资源与监控工具对应关系 系统指标 → 数据库行为 → 判断依据 CPU 关键指标 us / sy / wa r 运行队列 b 阻塞进程 数据库表现 sy 高 → 连接开销大 wa 高 → 等磁盘 单核满 → 大查询 内存 关键指标 si / so 换页 available 可用 shared_buffers 数据库表现 si/so > 0 → 内存不足 work_mem 过大 缓存命中率下降 磁盘 I/O 关键指标 %util 利用率 await 等待时间 r/s w/s 读写速率 数据库表现 检查点刷盘 WAL 顺序写 随机读延迟高 网络 关键指标 吞吐量 retrans 重传 连接数 数据库表现 大结果集传输 流复制延迟 连接数过多 监控工具 top / mpstat vmstat / free iostat -x sar -n DEV ss -s pg_stat_* 典型判断路径 CPU wa 高 + iostat await 高 检查点 / WAL 写入压力 调大 max_wal_size / 分散 I/O vmstat si/so > 0 物理内存不足 / work_mem 过大 降低 work_mem / 减少并发 sar retrans 增长 网络质量 / 大结果集传输 排查网络 / 优化查询返回量

把系统指标与数据库行为关联起来

单独看某一类资源容易误判。例如 CPU 空闲但查询慢,可能是 I/O 等待;磁盘 %util 不高但 await 高,可能是存储延迟;内存充足但 si/so 活跃,可能是 work_mem 与并发乘积超过了物理内存。正确的做法是同时采集四类指标,并与 PostgreSQL 的 pg_stat_activity、pg_stat_bgwriter、pg_stat_database 对照。

一个实用的关联方法是:先用 top 和 vmstat 判断瓶颈大类,再用 iostat 或 sar 细化到具体设备或网络接口,最后回到数据库视图确认是哪些查询、哪些后台进程在制造这些负载。例如 pg_stat_activity 中 state = 'active' 且 wait_event_type = 'IO' 的会话,往往对应 iostat 中高 await 的设备。

不要只看瞬时值。CPU 和 I/O 都有明显的周期性,检查点、备份、批量任务会造成短时尖峰。用 sar 保留历史数据,结合业务高峰时段判断,才能区分“正常波动”和“真实瓶颈”。

建立基线比记住阈值更重要

不同硬件、不同数据量、不同访问模式下,合理的 await、%util、si/so 数值差异很大。与其死记“await 超过 20ms 就有问题”,不如在系统稳定期采集一段基线:记录正常业务时段的 CPU 分布、磁盘 await 范围、内存换页情况、网络吞吐。之后出现性能问题时,与基线对比,偏离越明显越值得优先排查。

采集基线可以用 sar -o /var/log/sa/sa$(date +%d) 1 60 持续记录,也可以用 vmstat 60 1440 跑一整天。关键是固定采集频率和时段,让数据可比。PostgreSQL 侧则定期记录 pg_stat_bgwriter 的检查点次数和 pg_stat_database 的缓存命中率,与系统指标放在同一时间轴上观察。

当四类资源指标与数据库内部统计能够互相对照时,容量规划才有依据:CPU 核数决定并行查询上限,内存决定 shared_buffers 与 work_mem 的平衡点,磁盘 IOPS 和吞吐决定检查点与 WAL 策略,网络带宽决定复制和批量导出的可行规模。系统资源监控不是孤立地看数字,而是把操作系统的“体征”翻译成 PostgreSQL 的“行为”,再据此做出可验证的调整。