shared_buffers 共享缓冲区
shared_buffers 是 PostgreSQL 中最重要、也最容易调错的内存参数。它决定了数据库自己管理多少块(block)缓存,是所有会话共享的一块区域。改小了会让查询频繁走磁盘,改大了会挤压操作系统页缓存、甚至导致实例起不来。本节把它的默认值、25% 经验法则的来龙去脉、pg_buffercache 观测手段和落地流程讲清楚。
前置知识
阅读本节前,建议先了解:ALTER SYSTEM 动态配置
基础概念
PostgreSQL 的内存分层
要理解 shared_buffers,先要看清 PostgreSQL 的内存被分成两个大区。
┌──────────────────────────────────────────────────────────────┐
│ PostgreSQL 内存布局 │
├──────────────────────────────┬───────────────────────────────┤
│ 共享内存区 │ 本地内存区 │
│ (整个实例一份) │ (每个后端进程一份) │
├──────────────────────────────┼───────────────────────────────┤
│ shared_buffers 数据页缓存 │ work_mem 排序/哈希 │
│ wal_buffers WAL 缓冲 │ maintenance_work_mem 维护操作 │
│ 各种小缓冲区 │ temp_buffers 临时表缓冲 │
│ (锁表、事务状态缓存…) │ 各种会话级缓冲 │
└──────────────────────────────┴───────────────────────────────┘
↑ 本节重点 ↑ 后续几节逐一展开shared_buffers 是共享内存区中最大的一块,在实例启动时一次性分配,运行期间大小固定(这也是它必须是 postmaster context 的原因)。
它是干什么的
数据库读写的最小单位是块(block / page),默认 block_size = 8192(8kB)。任何一次数据访问,都要先把目标块从磁盘读进内存。shared_buffers 就是 PostgreSQL 自己维护的这块「内存页池」。
查询要读某一行
│
▼
┌─────────────────┐ 命中(hit) ┌──────────────┐
│ shared_buffers │ ─────────────▶ │ 直接返回数据 │
└─────────────────┘ └──────────────┘
│ 未命中(miss)
▼
┌─────────────────┐
│ 操作系统页缓存 │ 命中则从内存读,未命中才真正读盘
└─────────────────┘
│
▼
磁盘双层缓存:PostgreSQL 与操作系统
这是理解 shared_buffers 取值的关键。PostgreSQL 读一个块,实际上可能经过两层缓存:
| 层次 | 管理者 | 可见性 | 特点 |
|---|---|---|---|
| 第一层 | PostgreSQL 自身(shared_buffers) | 数据库内可见,可观测 | 大小固定,有 usagecount 等元信息 |
| 第二层 | 操作系统页缓存(page cache) | 数据库不可见 | 大小动态,由内核统一调度 |
所以「给 shared_buffers 越大越好」是错的——它挤掉的正是第二层的操作系统页缓存,而第二层同样能加速读取。
默认值与单位
SELECT name, setting, unit, boot_val, context, min_val, max_val, vartype
FROM pg_settings
WHERE name = 'shared_buffers'; name | setting | unit | boot_val | context | min_val | max_val | vartype
-----------------+---------+------+----------+-------------+---------+---------+---------
shared_buffers | 16384 | 8kB | 16384 | postmaster | 16 | | integer| 项目 | 说明 |
|---|---|
| 默认值 | 128MB(官方文档表述为「typically 128 megabytes」) |
| 最小值 | 128kB(即 min_val = 16 块 × 8kB) |
| 单位 | 不给单位时按块解释(BLCKSZ 字节,通常 8kB) |
| context | postmaster——必须重启才能改 |
-- 显示的 setting 是「块数」,乘以 unit 才是真实字节数
SELECT setting::bigint * 8 * 1024 AS bytes FROM pg_settings WHERE name = 'shared_buffers';
-- 16384 * 8192 = 134217728 = 128MB注意 setting 的单位
pg_settings.setting 对 shared_buffers 返回的是块数(unit = 8kB),不是字节数。直接用 setting 做除法会得到错误结论,务必乘上 unit。
语法与用法
配置方式
# postgresql.conf(推荐:写进主配置文件)
shared_buffers = 8GB-- ALTER SYSTEM(写入 postgresql.auto.conf)
ALTER SYSTEM SET shared_buffers = '8GB';# 命令行启动参数(优先级最高,会挡住配置文件)
postgres -c shared_buffers=8GB -D /var/lib/postgresql/18/main生效方式:必须重启
-- 改完先写入,再确认是否进入「待重启」状态
ALTER SYSTEM SET shared_buffers = '8GB';
SELECT name, setting, pending_restart FROM pg_settings WHERE name = 'shared_buffers'; name | setting | pending_restart
-----------------+---------+-----------------
shared_buffers | 16384 | t# 安排低峰窗口重启
pg_ctl restart -D /var/lib/postgresql/18/main -m fast-- 重启后验证:16384 -> 1048576 块,即 8GB
SELECT name, setting, unit, source, pending_restart
FROM pg_settings WHERE name = 'shared_buffers';开启 pg_buffercache 扩展观测
-- 需要超级用户执行一次
CREATE EXTENSION IF NOT EXISTS pg_buffercache;
-- 最省的一次性汇总(性能开销极小)
SELECT * FROM pg_buffercache_summary(); buffers_used | buffers_unused | buffers_dirty | buffers_pinned | usagecount_avg
--------------+----------------+---------------+----------------+----------------
262144 | 32 | 145 | 0 | 2.833101-- 按使用次数分布看缓存「热度」
SELECT * FROM pg_buffercache_usage_counts() ORDER BY usage_count; usage_count | buffers | dirty | pinned
-------------+---------+-------+--------
0 | 10240 | 0 | 0
1 | 14336 | 671 | 0
2 | 1024 | 188 | 0
3 | 256 | 21 | 0
4 | 48 | 7 | 0
5 | 2048 | 106 | 0让缓存统计归零后重新观察
-- 会话级:只影响当前会话(也可以 ALTER SYSTEM + reload 做全局重置)
SET stats_reset = ...; -- 不存在此参数,见下方说明-- 正确做法:重置统计计数器要用函数
SELECT pg_stat_reset(); -- 重置所有统计
SELECT pg_stat_reset_shared('bgwriter'); -- 只重置 bgwriter 计数详细说明
一、25% 经验法则的来源与边界
官方文档原文的表述是:
If you have a dedicated database server with 1GB or more of RAM, a reasonable starting value for
shared_buffersis 25% of the memory in your system.
注意三个限定词:
| 限定词 | 含义 |
|---|---|
| dedicated | 机器专用于数据库,没有别的吃内存的服务 |
| 1GB or more | 内存小于 1GB 时,应取更小的比例,给操作系统留够空间 |
| starting value | 这是起点,不是终点,要结合实测调整 |
机器内存 建议起点 说明
─────────────────────────────────────────────
512MB ~64MB < 1GB,比例要更保守
1GB 256MB 刚好够 25% 的边缘
4GB 1GB
8GB 2GB
16GB 4GB
32GB 8GB
64GB 16GB
128GB 32GB二、40% 上限为什么存在
官方紧接着写道:
it is unlikely that an allocation of more than 40% of RAM to
shared_bufferswill work better than a smaller amount.
原因就是前面说的双层缓存:PostgreSQL 自己能缓存的块,操作系统页缓存同样能缓存。而操作系统页缓存的管理粒度更粗、开销更低、命中率通常更高,因为它不需要维护 usagecount、脏页标记等额外信息。
把 shared_buffers 从 25% 提到 60%:
✅ PostgreSQL 内部命中率上升
❌ 操作系统页缓存被挤压,第二层命中率下降
❌ 大页/shmem 分配可能失败,实例起不来
净效果:往往持平甚至变差三、shared_buffers 与 max_wal_size 的联动
官方文档有一句容易被忽略的话:
Larger settings for
shared_buffersusually require a corresponding increase inmax_wal_size, in order to spread out the process of writing large quantities of new or changed data over a longer period of time.
逻辑链是这样的:
shared_buffers 变大
│
▼
能容纳的脏页(dirty pages)更多
│
▼
一次 checkpoint 要刷的脏页总量变大
│
▼
如果 max_wal_size 不变,checkpoint 会更频繁触发
│
▼
需要同步调大 max_wal_size# 一个配套调整的例子(16GB 内存机器)
shared_buffers = 4GB
max_wal_size = 8GB # 随 shared_buffers 一起放大
min_wal_size = 1GB
checkpoint_completion_target = 0.9四、huge_pages:大页能显著降低内存管理开销
SELECT name, setting, context FROM pg_settings WHERE name IN ('huge_pages', 'huge_page_size'); name | setting | context
----------------+---------+-------------
huge_pages | try | postmaster
huge_page_size | 0 | postmaster| 取值 | 行为 | 风险 |
|---|---|---|
try(默认) | 尝试申请大页,失败则退回普通页 | 无(最安全) |
on | 必须用大页,申请失败则实例启动失败 | 配置不当会起不来 |
off | 不申请大页 | 无 |
# Linux 上启用大页(需要先在 OS 层配置 vm.nr_hugepages)
huge_pages = try
huge_page_size = 0 # 0 表示用系统默认大页尺寸(常见 2MB)# 查看操作系统的大页配置
grep -i huge /proc/meminfo
# HugePages_Total: 4096
# HugePages_Free: 4096
# Hugepagesize: 2048 kB实际状态要看 huge_pages_status
huge_pages 是「请求」,实际有没有用上大页,要看预设参数 huge_pages_status:
SHOW huge_pages_status; -- on / off / unknown五、shared_memory_type:共享内存的实现方式
SELECT name, setting, context, enumvals FROM pg_settings WHERE name = 'shared_memory_type'; name | setting | context | enumvals
--------------------+---------+-------------+-----------------------------
shared_memory_type | mmap | postmaster | {mmap,sysv,windows}| 取值 | 平台 | 说明 |
|---|---|---|
mmap | Linux 等 | 默认(首个受支持的值即默认值),匿名共享内存 |
sysv | 通用 | System V 共享内存,官方不推荐,通常需要调内核参数 |
windows | Windows | Windows 共享内存 |
别随便改成 sysv
官方明确说 sysv 在任何平台上都不是默认值,且一般不被推荐——因为它往往需要非默认的内核设置才能做大额分配。只有在 Linux 上 shared_memory_type = mmap 时,huge_pages 才被支持。
六、缓冲区淘汰:时钟扫描与 usagecount
shared_buffers 满了之后,新块要挤掉旧块。PostgreSQL 用的是时钟扫描(clock-sweep)算法,每个缓冲区有一个 usagecount(0~5):
缓冲区被访问 → usagecount +1(上限 5)
需要腾位置 → 指针顺时针扫描,优先淘汰 usagecount 低的
被扫到时 usagecount -1,降到 0 才真正淘汰
┌───────┐
│ buf 1 │ uc=3
├───────┤
│ buf 2 │ uc=5 ← 热点,最难被淘汰
├───────┤
│ buf 3 │ uc=0 ← 下一个被淘汰的候选
└───────┘
▲
│ 时钟指针-- 观察 usagecount 分布:uc=5 的块占比高,说明热点稳定
SELECT usage_count, buffers, dirty, pinned
FROM pg_buffercache_usage_counts()
ORDER BY usage_count;七、脏页谁来刷:bgwriter 与 checkpointer 的分工
shared_buffers 里的块被修改后变成脏页(dirty page),必须写回磁盘。这项工作的分工:
| 角色 | 进程 | 职责 |
|---|---|---|
| 后台写进程 | bgwriter | 平时平滑地把脏页刷出去,避免查询自己刷(会让查询变慢) |
| 检查点进程 | checkpointer | 周期性地把 checkpoint 之前的所有脏页刷盘,缩短崩溃恢复时间 |
| 后台工作进程 | 普通 backend | 兜底,缓存不足时自己刷脏页 |
-- 脏页有多少(瞬时值)
SELECT buffers_dirty, buffers_used,
round(100.0 * buffers_dirty / nullif(buffers_used, 0), 2) AS dirty_pct
FROM pg_buffercache_summary(); buffers_dirty | buffers_used | dirty_pct
---------------+--------------+-----------
145 | 262144 | 0.06脏页占比长期偏高,说明 bgwriter 刷得不够勤,可以关注以下参数:
# 后台写进程相关(属于本地内存/IO 调优范畴,此处仅列出)
bgwriter_delay = 200ms
bgwriter_lru_maxpages = 100
bgwriter_lru_multiplier = 2.0
bgwriter_flush_after = 512kB八、缓存命中率怎么算
命中率来自 pg_stat_database 的累计计数器:
SELECT
datname,
blks_hit,
blks_read,
round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_ratio_pct
FROM pg_stat_database
WHERE datname = current_database(); datname | blks_hit | blks_read | hit_ratio_pct
---------+----------+-----------+---------------
mydb | 98452103 | 213445 | 99.78命中率高不等于一切正常
blks_hit/blks_read 只统计共享缓冲区这一层。命中率 99% 也可能是「读了大量重复的小块」,而真正的问题在别处(例如排序落盘)。命中率低是明确信号,命中率高不代表没问题。
更重要的是:这个计数器包含数据库内部访问(系统目录、索引等),不能完全等同于「用户查询的缓存效果」。
九、pg_buffercache 视图的列
| 列名 | 类型 | 含义 |
|---|---|---|
bufferid | integer | 缓冲区 ID,范围 1..shared_buffers |
relfilenode | oid | 关系的 filenode |
reltablespace | oid | 所属表空间 OID |
reldatabase | oid | 所属数据库 OID(共享目录为 0) |
relforknumber | smallint | 关系内的 fork 号 |
relblocknumber | bigint | 关系内的页号 |
isdirty | boolean | 是否是脏页 |
usagecount | smallint | 时钟扫描访问计数 |
pinning_backends | integer | 正在 pin 这个缓冲区的后端数量 |
未使用的缓冲区除了 bufferid 之外所有列为 NULL。
关联 pg_class 一定要过滤数据库
缓冲区是全库共享的,里面既有当前库的页,也有别的库的页。直接 JOIN pg_class 可能得到错误的关联结果。官方建议:把关联限制在 reldatabase 为当前数据库 OID 或 0 的行。
-- 正确:先限定数据库,再用 pg_relation_filenode 关联
SELECT n.nspname, c.relname, count(*) AS buffers
FROM pg_buffercache b
JOIN pg_class c ON b.relfilenode = pg_relation_filenode(c.oid)
AND b.reldatabase IN (0, (SELECT oid FROM pg_database
WHERE datname = current_database()))
JOIN pg_namespace n ON n.oid = c.relnamespace
GROUP BY n.nspname, c.relname
ORDER BY 3 DESC
LIMIT 10;十、共享内存大小与内核限制
在 shared_memory_type = sysv 或某些平台下,shared_buffers 过大会撞上内核限制,导致 initdb 时就被自动调小,或实例启动失败。
常见内核限制(Linux):
SHMMAX 单个共享内存段最大字节数
SHMALL 系统共享内存总页数
vm.overcommit_memory 过量分配策略
当 shared_buffers 超过 SHMMAX:
- 实例可能启动失败
- 或 initdb 阶段就把默认值调小(官方提到默认值可能「less if your kernel settings will not support it」)# 查看 Linux 共享内存限制
ipcs -l
# ------ Shared Memory Limits --------
# max seg size (kbytes) = 18014398509481984-- 查看实例实际申请到的共享内存(本视图需要 pg_shmem_allocations 可读权限)
SELECT name, pg_size_pretty(size) AS size
FROM pg_shmem_allocations
ORDER BY size DESC
LIMIT 10;实战示例
场景一:把 shared_buffers 从 128MB 调到 8GB 的完整流程
前提:一台 32GB 内存、专用于数据库的机器(按 25% 取 8GB)。
-- 第 1 步:记录基线——当前值、命中率、缓存使用情况
SELECT name, setting::bigint * 8192 AS bytes, pending_restart
FROM pg_settings WHERE name = 'shared_buffers'; name | bytes | pending_restart
-----------------+-----------+-----------------
shared_buffers | 134217728 | f-- 记录当前命中率作为对照
SELECT datname, blks_hit, blks_read,
round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_pct
FROM pg_stat_database WHERE datname = current_database(); datname | blks_hit | blks_read | hit_pct
---------+----------+-----------+---------
mydb | 12045311 | 884512 | 93.17# 第 2 步:确认机器内存与内核限制
free -h
# total used free
# Mem: 31Gi 8.2Gi 21Gi
ipcs -l | head -20
# max seg size (kbytes) = 18014398509481984 ← 足够大,不撞 SHMMAX-- 第 3 步:写入新值
ALTER SYSTEM SET shared_buffers = '8GB';-- 第 4 步:确认进入待重启状态
SELECT name, setting, pending_restart FROM pg_settings WHERE name = 'shared_buffers'; name | setting | pending_restart
-----------------+---------+-----------------
shared_buffers | 16384 | t# 第 5 步:低峰期重启(fast 模式:断开现有连接,尽快关闭)
pg_ctl restart -D /var/lib/postgresql/18/main -m fast -l /var/log/pg18.log-- 第 6 步:重启后验证大小(8GB = 1048576 块)
SELECT name, setting, unit, source, pending_restart
FROM pg_settings WHERE name = 'shared_buffers'; name | setting | unit | source | pending_restart
-----------------+---------+------+--------------------+-----------------
shared_buffers | 1048576 | 8kB | configuration file | f-- 第 7 步:验证大页状态(若已开启 huge_pages)
SHOW huge_pages;
SHOW huge_pages_status;-- 第 8 步:让缓存「重新长满」并观察指标(业务跑一段时间后再看)
SELECT * FROM pg_buffercache_summary();
SELECT datname, blks_hit, blks_read,
round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_pct
FROM pg_stat_database WHERE datname = current_database(); datname | blks_hit | blks_read | hit_pct
---------+----------+-----------+---------
mydb | 15882340 | 210334 | 98.69命中率从 93.17% 提升到 98.69%,说明扩大 shared_buffers 对这套负载确实有效。
场景二:用 pg_buffercache 找出「谁在占缓存」
-- 需要先安装扩展
CREATE EXTENSION IF NOT EXISTS pg_buffercache;-- 按「占用缓冲区数量」排序,找出缓存里最大的那些关系
SELECT
n.nspname AS schema,
c.relname AS relation,
count(*) AS buffers,
pg_size_pretty(count(*) * 8192) AS cached_size,
round(avg(b.usagecount), 2) AS avg_usage,
count(*) FILTER (WHERE b.isdirty) AS dirty_buffers
FROM pg_buffercache b
JOIN pg_class c
ON b.relfilenode = pg_relation_filenode(c.oid)
AND b.reldatabase IN (0, (SELECT oid FROM pg_database
WHERE datname = current_database()))
JOIN pg_namespace n ON n.oid = c.relnamespace
GROUP BY n.nspname, c.relname
ORDER BY buffers DESC
LIMIT 15; schema | relation | buffers | cached_size | avg_usage | dirty_buffers
--------+----------------------+---------+-------------+-----------+---------------
public | orders | 524288 | 4096 MB | 3.42 | 18
public | orders_pkey | 131072 | 1024 MB | 4.11 | 3
public | pg_attribute | 1024 | 8 MB | 2.88 | 0
public | t_order_items | 65536 | 512 MB | 3.01 | 12
(4 rows)结论:orders 一张表就吃掉 4GB 缓存,是绝对的访问热点;orders_pkey 的 avg_usage 高达 4.11,说明索引访问极频繁。
-- 进一步:看有多少缓存是「只用过一次」的冷数据(uc=1 占比高说明存在一次性扫描污染缓存)
SELECT
usage_count,
buffers,
round(100.0 * buffers / sum(buffers) OVER (), 2) AS pct
FROM pg_buffercache_usage_counts()
ORDER BY usage_count; usage_count | buffers | pct
-------------+---------+-------
0 | 262144 | 25.0
1 | 419430 | 40.0
2 | 157286 | 15.0
3 | 104857 | 10.0
4 | 52428 | 5.0
5 | 52429 | 5.0usage_count = 1 占 40%,说明有大量数据被读了一次就再也不用了(典型的一次性全表扫描),可以考虑为这类查询做索引或分区裁剪。
场景三:命中率低的诊断路径
-- 第 1 步:先看全局命中率
SELECT
datname,
blks_hit,
blks_read,
round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_pct,
temp_files,
pg_size_pretty(temp_bytes) AS temp_size
FROM pg_stat_database
ORDER BY blks_read DESC
LIMIT 5; datname | blks_hit | blks_read | hit_pct | temp_files | temp_size
---------+----------+-----------+---------+------------+-----------
mydb | 9845112 | 1884521 | 83.93 | 312 | 2 GB
(1 row)命中率 83.93% 偏低,且 temp_files = 312、temp_bytes = 2GB——排序/哈希落盘也是症状之一。
-- 第 2 步:判断是「缓存太小」还是「访问模式差」
SELECT * FROM pg_buffercache_summary(); buffers_used | buffers_unused | buffers_dirty | buffers_pinned | usagecount_avg
--------------+----------------+---------------+----------------+----------------
16200 | 184 | 210 | 0 | 1.982341buffers_unused 几乎为 0(16384 总数几乎全部被用满)、usagecount_avg 只有 1.98——说明缓存已经不够用,大量块刚进来就被挤出去。此时扩大 shared_buffers 会有直接收益。
反过来,如果 buffers_unused 还有大量空闲、usagecount_avg 很低,那问题就不在缓存容量,而在查询本身。
-- 第 3 步:排除临时文件造成的误判,确认真正的物理读量
SELECT datname,
blks_read AS shared_read,
round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 2) AS hit_pct
FROM pg_stat_database
WHERE datname = current_database();注意事项
1. pg_settings.setting 的单位是「块」,不是字节。 直接当成字节会导致倍数判断错误。
-- 错误!把块数当字节数
SELECT setting FROM pg_settings WHERE name = 'shared_buffers'; -- 16384,被误读为 16kB-- 正确:乘上 unit
SELECT setting::bigint * 8192 AS bytes FROM pg_settings WHERE name = 'shared_buffers';2. 改 shared_buffers 必须重启,reload 一定无效。 它是 postmaster context。
# 不推荐:以为 reload 能生效
pg_ctl reload -D /var/lib/postgresql/18/main # shared_buffers 不会有任何变化3. 别超过内存的 40%。 超过后通常不会更好,还会挤压操作系统页缓存。
# 不推荐:128GB 机器给 96GB(75%)
shared_buffers = 96GB4. 内存小于 1GB 时不要机械套用 25%。 要给操作系统留够内存。
# 不推荐:512MB 内存的机器给 128MB
shared_buffers = 128MB
# 建议取更小比例,例如 64MB5. 扩大 shared_buffers 不要忘记同步调大 max_wal_size。 否则 checkpoint 频率上升,写放大更明显。
# 不推荐:只改了一个
shared_buffers = 8GB
max_wal_size = 1GB # 相对太小,checkpoint 会被频繁触发6. 把 shared_memory_type 改成 sysv 是常见错误。 官方不推荐,且会引入内核参数依赖。
# 不推荐
shared_memory_type = sysv7. huge_pages = on 在生产环境风险很高。 大页申请失败会让实例直接起不来。
# 风险配置:OS 层大页没配好时直接启动失败
huge_pages = on
# 更稳妥的做法
huge_pages = try8. 关联 pg_buffercache 与 pg_class 时忘记过滤数据库,会得到错误结果。
-- 错误!没有限定 reldatabase,可能关联到别的库的同名关系
SELECT count(*) FROM pg_buffercache b JOIN pg_class c ON b.relfilenode = c.relfilenode;9. 高命中率不能证明缓存配置合理。 命中率高也可能是「反复读同一小批数据」,真正瓶颈在排序落盘。
-- 不推荐:只看命中率就宣布缓存没问题
SELECT round(100.0*blks_hit/(blks_hit+blks_read),2) FROM pg_stat_database;
-- 还要同时看 temp_files / temp_bytes10. pg_buffercache_evict* 系列只能用于测试,禁止在生产使用。 官方在文档中明确说明它们是仅供开发者测试的。
-- 严禁在生产执行!
SELECT pg_buffercache_evict_all();11. 不要用 UPDATE pg_settings 试图改 shared_buffers。 那是会话级操作,对 postmaster 参数无效且会误导自己。
-- 错误!
UPDATE pg_settings SET setting = '8388608' WHERE name = 'shared_buffers';12. 调整前必须先确认内核共享内存限制。 尤其在容器/受限环境中,可能会直接启动失败。
# 调整前必做
ipcs -l | head -20
cat /proc/sys/kernel/shmmax最佳实践
- 以 25% 为起点,在 25%~40% 区间内做 A/B 验证,不要凭感觉调。
-- 每次调整后,等业务跑满一轮再采集对照数据
SELECT datname, blks_hit, blks_read,
round(100.0*blks_hit/nullif(blks_hit+blks_read,0),2) AS hit_pct
FROM pg_stat_database WHERE datname = current_database();- 调整前记录基线(值 + 命中率 + 缓存摘要),调整后对比。
CREATE TABLE IF NOT EXISTS dba_sb_baseline(
taken_at timestamptz DEFAULT now(),
setting_blocks bigint,
hit_pct numeric,
buffers_used int,
avg_usage float8
);
INSERT INTO dba_sb_baseline(setting_blocks, hit_pct, buffers_used, avg_usage)
SELECT (SELECT setting::bigint FROM pg_settings WHERE name='shared_buffers'),
(SELECT round(100.0*blks_hit/nullif(blks_hit+blks_read,0),2)
FROM pg_stat_database WHERE datname=current_database()),
(SELECT buffers_used FROM pg_buffercache_summary()),
(SELECT usagecount_avg FROM pg_buffercache_summary());- 专机专用永远是前提。 同一台机器上跑 Redis、Elasticsearch 时,
shared_buffers的取值要相应下调。
内存规划示例(32GB 机器,PostgreSQL + 少量其他服务):
shared_buffers 6GB
操作系统页缓存 12GB
其他服务 8GB
预留 6GB- 配套调整
max_wal_size与checkpoint_completion_target。
shared_buffers = 8GB
max_wal_size = 8GB
min_wal_size = 1GB
checkpoint_completion_target = 0.9- 在 Linux 上考虑显式启用大页(
huge_pages = try),并先在 OS 层配好vm.nr_hugepages。
# OS 层:按需设置大页数量(2MB 页,4GB shared_buffers 需要 2048 个大页)
sysctl -w vm.nr_hugepages=2048- 把
pg_buffercache作为常规观测手段纳入巡检。 它的summary/usage_counts两个函数开销极小。
SELECT * FROM pg_buffercache_summary();
SELECT * FROM pg_buffercache_usage_counts() ORDER BY usage_count;- 在容器化部署中显式设定内存上限,并让
shared_buffers与 limit 匹配。
# 示例:Kubernetes 资源限制下,shared_buffers 不要超过 limit 的 25%~40%
resources:
limits:
memory: "8Gi"
# 对应 shared_buffers 取 2GB 左右- 区分「数据库共享缓存」和「操作系统页缓存」的观测口径。 只盯
shared_buffers会漏掉一半信息。
# 看操作系统页缓存回收情况
cat /proc/vmstat | grep -E "pgpg|pswp"- 避免在业务高峰期做需要重启的内存调整。
shared_buffers改变会让缓存「冷启动」,短期内命中率骤降。
# 好:低峰窗口
pg_ctl restart -D /var/lib/postgresql/18/main -m fast- 缓存被一次性大查询污染时,优先优化 SQL,而不是无脑加内存。
-- 先找出这类查询(需 pg_stat_statements)
SELECT queryid, calls, rows, shared_blks_read, shared_blks_hit,
round(100.0*shared_blks_hit/nullif(shared_blks_hit+shared_blks_read,0),2) AS hit_pct
FROM pg_stat_statements
ORDER BY shared_blks_read DESC
LIMIT 10;- 每次重启后都要复核
pending_restart是否为false,确保配置真的落地。
SELECT name, setting FROM pg_settings WHERE pending_restart;- 把
shared_buffers的取值写进版本控制的配置模板,并在注释里写明取值依据。
# 依据:32GB 专机,25% 法则取 8GB;命中率由 93% 提升至 98.7%
shared_buffers = 8GB下一节
参考链接
- PostgreSQL 官方文档:Resource Consumption / Memory(runtime-config-resource)
- PostgreSQL 官方文档:pg_buffercache(pgbuffercache)
- PostgreSQL 官方文档:The Statistics Collector(monitoring-stats)
- PostgreSQL 官方文档:File Locations / data_directory(runtime-config-file-locations)
- PostgreSQL 官方文档:Managing Kernel Resources(kernel-resources)
- PostgreSQL 官方文档:Preset Options / huge_pages_status(runtime-config-preset)
- PostgreSQL 官方文档:Write Ahead Log / Checkpoints(runtime-config-wal)
- PostgreSQL 官方文档:Monitoring Database Activity(monitoring)