Skip to content

shared_buffers 共享缓冲区 ​

shared_buffers 是 PostgreSQL 中最重要、也最容易调错的内存参数。它决定了数据库自己管理多少块(block)缓存,是所有会话共享的一块区域。改小了会让查询频繁走磁盘,改大了会挤压操作系统页缓存、甚至导致实例起不来。本节把它的默认值、25% 经验法则的来龙去脉、pg_buffercache 观测手段和落地流程讲清楚。

前置知识

阅读本节前,建议先了解:ALTER SYSTEM 动态配置

基础概念 ​

PostgreSQL 的内存分层 ​

要理解 shared_buffers,先要看清 PostgreSQL 的内存被分成两个大区。

text
┌──────────────────────────────────────────────────────────────┐
│                   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 自己维护的这块「内存页池」。

text
   查询要读某一行
          │
          ▼
   ┌─────────────────┐   命中(hit)   ┌──────────────┐
   │  shared_buffers │ ─────────────▶ │ 直接返回数据  │
   └─────────────────┘                 └──────────────┘
          │ 未命中(miss)
          ▼
   ┌─────────────────┐
   │ 操作系统页缓存    │  命中则从内存读,未命中才真正读盘
   └─────────────────┘
          │
          ▼
        磁盘

双层缓存:PostgreSQL 与操作系统 ​

这是理解 shared_buffers 取值的关键。PostgreSQL 读一个块,实际上可能经过两层缓存:

层次管理者可见性特点
第一层PostgreSQL 自身(shared_buffers)数据库内可见,可观测大小固定,有 usagecount 等元信息
第二层操作系统页缓存(page cache)数据库不可见大小动态,由内核统一调度

所以「给 shared_buffers 越大越好」是错的——它挤掉的正是第二层的操作系统页缓存,而第二层同样能加速读取。

默认值与单位 ​

sql
SELECT name, setting, unit, boot_val, context, min_val, max_val, vartype
FROM pg_settings
WHERE name = 'shared_buffers';
text
      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)
contextpostmaster——必须重启才能改
sql
-- 显示的 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。

语法与用法 ​

配置方式 ​

ini
# postgresql.conf(推荐:写进主配置文件)
shared_buffers = 8GB
sql
-- ALTER SYSTEM(写入 postgresql.auto.conf)
ALTER SYSTEM SET shared_buffers = '8GB';
bash
# 命令行启动参数(优先级最高,会挡住配置文件)
postgres -c shared_buffers=8GB -D /var/lib/postgresql/18/main

生效方式:必须重启 ​

sql
-- 改完先写入,再确认是否进入「待重启」状态
ALTER SYSTEM SET shared_buffers = '8GB';
SELECT name, setting, pending_restart FROM pg_settings WHERE name = 'shared_buffers';
text
      name       | setting | pending_restart
-----------------+---------+-----------------
 shared_buffers  | 16384   | t
bash
# 安排低峰窗口重启
pg_ctl restart -D /var/lib/postgresql/18/main -m fast
sql
-- 重启后验证:16384 -> 1048576 块,即 8GB
SELECT name, setting, unit, source, pending_restart
FROM pg_settings WHERE name = 'shared_buffers';

开启 pg_buffercache 扩展观测 ​

sql
-- 需要超级用户执行一次
CREATE EXTENSION IF NOT EXISTS pg_buffercache;

-- 最省的一次性汇总(性能开销极小)
SELECT * FROM pg_buffercache_summary();
text
 buffers_used | buffers_unused | buffers_dirty | buffers_pinned | usagecount_avg
--------------+----------------+---------------+----------------+----------------
       262144 |             32 |           145 |              0 |       2.833101
sql
-- 按使用次数分布看缓存「热度」
SELECT * FROM pg_buffercache_usage_counts() ORDER BY usage_count;
text
 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

让缓存统计归零后重新观察 ​

sql
-- 会话级:只影响当前会话(也可以 ALTER SYSTEM + reload 做全局重置)
SET stats_reset = ...;   -- 不存在此参数,见下方说明
sql
-- 正确做法:重置统计计数器要用函数
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_buffers is 25% of the memory in your system.

注意三个限定词:

限定词含义
dedicated机器专用于数据库,没有别的吃内存的服务
1GB or more内存小于 1GB 时,应取更小的比例,给操作系统留够空间
starting value这是起点,不是终点,要结合实测调整
text
  机器内存      建议起点        说明
  ─────────────────────────────────────────────
   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_buffers will work better than a smaller amount.

原因就是前面说的双层缓存:PostgreSQL 自己能缓存的块,操作系统页缓存同样能缓存。而操作系统页缓存的管理粒度更粗、开销更低、命中率通常更高,因为它不需要维护 usagecount、脏页标记等额外信息。

text
   把 shared_buffers 从 25% 提到 60%:
     ✅ PostgreSQL 内部命中率上升
     ❌ 操作系统页缓存被挤压,第二层命中率下降
     ❌ 大页/shmem 分配可能失败,实例起不来
     净效果:往往持平甚至变差

三、shared_buffers 与 max_wal_size 的联动 ​

官方文档有一句容易被忽略的话:

Larger settings for shared_buffers usually require a corresponding increase in max_wal_size, in order to spread out the process of writing large quantities of new or changed data over a longer period of time.

逻辑链是这样的:

text
shared_buffers 变大
     │
     ▼
能容纳的脏页(dirty pages)更多
     │
     ▼
一次 checkpoint 要刷的脏页总量变大
     │
     ▼
如果 max_wal_size 不变,checkpoint 会更频繁触发
     │
     ▼
需要同步调大 max_wal_size
ini
# 一个配套调整的例子(16GB 内存机器)
shared_buffers = 4GB
max_wal_size = 8GB          # 随 shared_buffers 一起放大
min_wal_size = 1GB
checkpoint_completion_target = 0.9

四、huge_pages:大页能显著降低内存管理开销 ​

sql
SELECT name, setting, context FROM pg_settings WHERE name IN ('huge_pages', 'huge_page_size');
text
      name      | setting |   context
----------------+---------+-------------
 huge_pages     | try     | postmaster
 huge_page_size | 0       | postmaster
取值行为风险
try(默认)尝试申请大页,失败则退回普通页无(最安全)
on必须用大页,申请失败则实例启动失败配置不当会起不来
off不申请大页无
ini
# Linux 上启用大页(需要先在 OS 层配置 vm.nr_hugepages)
huge_pages = try
huge_page_size = 0          # 0 表示用系统默认大页尺寸(常见 2MB)
bash
# 查看操作系统的大页配置
grep -i huge /proc/meminfo
# HugePages_Total:    4096
# HugePages_Free:     4096
# Hugepagesize:       2048 kB

实际状态要看 huge_pages_status

huge_pages 是「请求」,实际有没有用上大页,要看预设参数 huge_pages_status:

sql
SHOW huge_pages_status;   -- on / off / unknown

五、shared_memory_type:共享内存的实现方式 ​

sql
SELECT name, setting, context, enumvals FROM pg_settings WHERE name = 'shared_memory_type';
text
        name        | setting |   context   |          enumvals
--------------------+---------+-------------+-----------------------------
 shared_memory_type | mmap    | postmaster  | {mmap,sysv,windows}
取值平台说明
mmapLinux 等默认(首个受支持的值即默认值),匿名共享内存
sysv通用System V 共享内存,官方不推荐,通常需要调内核参数
windowsWindowsWindows 共享内存

别随便改成 sysv

官方明确说 sysv 在任何平台上都不是默认值,且一般不被推荐——因为它往往需要非默认的内核设置才能做大额分配。只有在 Linux 上 shared_memory_type = mmap 时,huge_pages 才被支持。

六、缓冲区淘汰:时钟扫描与 usagecount ​

shared_buffers 满了之后,新块要挤掉旧块。PostgreSQL 用的是时钟扫描(clock-sweep)算法,每个缓冲区有一个 usagecount(0~5):

text
   缓冲区被访问 → usagecount +1(上限 5)
   需要腾位置   → 指针顺时针扫描,优先淘汰 usagecount 低的
                 被扫到时 usagecount -1,降到 0 才真正淘汰

        ┌───────┐
        │ buf 1 │ uc=3
        ├───────┤
        │ buf 2 │ uc=5  ← 热点,最难被淘汰
        ├───────┤
        │ buf 3 │ uc=0  ← 下一个被淘汰的候选
        └───────┘
            ▲
            │ 时钟指针
sql
-- 观察 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兜底,缓存不足时自己刷脏页
sql
-- 脏页有多少(瞬时值)
SELECT buffers_dirty, buffers_used,
       round(100.0 * buffers_dirty / nullif(buffers_used, 0), 2) AS dirty_pct
FROM pg_buffercache_summary();
text
 buffers_dirty | buffers_used | dirty_pct
---------------+--------------+-----------
           145 |       262144 |      0.06

脏页占比长期偏高,说明 bgwriter 刷得不够勤,可以关注以下参数:

ini
# 后台写进程相关(属于本地内存/IO 调优范畴,此处仅列出)
bgwriter_delay = 200ms
bgwriter_lru_maxpages = 100
bgwriter_lru_multiplier = 2.0
bgwriter_flush_after = 512kB

八、缓存命中率怎么算 ​

命中率来自 pg_stat_database 的累计计数器:

sql
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();
text
 datname | blks_hit | blks_read | hit_ratio_pct
---------+----------+-----------+---------------
 mydb    | 98452103 |    213445 |         99.78

命中率高不等于一切正常

blks_hit/blks_read 只统计共享缓冲区这一层。命中率 99% 也可能是「读了大量重复的小块」,而真正的问题在别处(例如排序落盘)。命中率低是明确信号,命中率高不代表没问题。

更重要的是:这个计数器包含数据库内部访问(系统目录、索引等),不能完全等同于「用户查询的缓存效果」。

九、pg_buffercache 视图的列 ​

列名类型含义
bufferidinteger缓冲区 ID,范围 1..shared_buffers
relfilenodeoid关系的 filenode
reltablespaceoid所属表空间 OID
reldatabaseoid所属数据库 OID(共享目录为 0)
relforknumbersmallint关系内的 fork 号
relblocknumberbigint关系内的页号
isdirtyboolean是否是脏页
usagecountsmallint时钟扫描访问计数
pinning_backendsinteger正在 pin 这个缓冲区的后端数量

未使用的缓冲区除了 bufferid 之外所有列为 NULL。

关联 pg_class 一定要过滤数据库

缓冲区是全库共享的,里面既有当前库的页,也有别的库的页。直接 JOIN pg_class 可能得到错误的关联结果。官方建议:把关联限制在 reldatabase 为当前数据库 OID 或 0 的行。

sql
-- 正确:先限定数据库,再用 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 时就被自动调小,或实例启动失败。

text
常见内核限制(Linux):
  SHMMAX       单个共享内存段最大字节数
  SHMALL       系统共享内存总页数
  vm.overcommit_memory   过量分配策略

当 shared_buffers 超过 SHMMAX:
  - 实例可能启动失败
  - 或 initdb 阶段就把默认值调小(官方提到默认值可能「less if your kernel settings will not support it」)
bash
# 查看 Linux 共享内存限制
ipcs -l
# ------ Shared Memory Limits --------
# max seg size (kbytes) = 18014398509481984
sql
-- 查看实例实际申请到的共享内存(本视图需要 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)。

sql
-- 第 1 步:记录基线——当前值、命中率、缓存使用情况
SELECT name, setting::bigint * 8192 AS bytes, pending_restart
FROM pg_settings WHERE name = 'shared_buffers';
text
      name       |   bytes   | pending_restart
-----------------+-----------+-----------------
 shared_buffers  | 134217728 | f
sql
-- 记录当前命中率作为对照
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();
text
 datname | blks_hit | blks_read | hit_pct
---------+----------+-----------+---------
 mydb    | 12045311 |    884512 |   93.17
bash
# 第 2 步:确认机器内存与内核限制
free -h
#               total        used        free
# Mem:           31Gi       8.2Gi        21Gi

ipcs -l | head -20
# max seg size (kbytes) = 18014398509481984   ← 足够大,不撞 SHMMAX
sql
-- 第 3 步:写入新值
ALTER SYSTEM SET shared_buffers = '8GB';
sql
-- 第 4 步:确认进入待重启状态
SELECT name, setting, pending_restart FROM pg_settings WHERE name = 'shared_buffers';
text
      name       | setting | pending_restart
-----------------+---------+-----------------
 shared_buffers  | 16384   | t
bash
# 第 5 步:低峰期重启(fast 模式:断开现有连接,尽快关闭)
pg_ctl restart -D /var/lib/postgresql/18/main -m fast -l /var/log/pg18.log
sql
-- 第 6 步:重启后验证大小(8GB = 1048576 块)
SELECT name, setting, unit, source, pending_restart
FROM pg_settings WHERE name = 'shared_buffers';
text
      name       | setting | unit |       source       | pending_restart
-----------------+---------+------+--------------------+-----------------
 shared_buffers  | 1048576 | 8kB  | configuration file | f
sql
-- 第 7 步:验证大页状态(若已开启 huge_pages)
SHOW huge_pages;
SHOW huge_pages_status;
sql
-- 第 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();
text
 datname | blks_hit | blks_read | hit_pct
---------+----------+-----------+---------
 mydb    | 15882340 |    210334 |   98.69

命中率从 93.17% 提升到 98.69%,说明扩大 shared_buffers 对这套负载确实有效。

场景二:用 pg_buffercache 找出「谁在占缓存」 ​

sql
-- 需要先安装扩展
CREATE EXTENSION IF NOT EXISTS pg_buffercache;
sql
-- 按「占用缓冲区数量」排序,找出缓存里最大的那些关系
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;
text
 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,说明索引访问极频繁。

sql
-- 进一步:看有多少缓存是「只用过一次」的冷数据(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;
text
 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.0

usage_count = 1 占 40%,说明有大量数据被读了一次就再也不用了(典型的一次性全表扫描),可以考虑为这类查询做索引或分区裁剪。

场景三:命中率低的诊断路径 ​

sql
-- 第 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;
text
 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——排序/哈希落盘也是症状之一。

sql
-- 第 2 步:判断是「缓存太小」还是「访问模式差」
SELECT * FROM pg_buffercache_summary();
text
 buffers_used | buffers_unused | buffers_dirty | buffers_pinned | usagecount_avg
--------------+----------------+---------------+----------------+----------------
        16200 |             184 |           210 |              0 |       1.982341

buffers_unused 几乎为 0(16384 总数几乎全部被用满)、usagecount_avg 只有 1.98——说明缓存已经不够用,大量块刚进来就被挤出去。此时扩大 shared_buffers 会有直接收益。

反过来,如果 buffers_unused 还有大量空闲、usagecount_avg 很低,那问题就不在缓存容量,而在查询本身。

sql
-- 第 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 的单位是「块」,不是字节。 直接当成字节会导致倍数判断错误。

sql
-- 错误!把块数当字节数
SELECT setting FROM pg_settings WHERE name = 'shared_buffers';   -- 16384,被误读为 16kB
sql
-- 正确:乘上 unit
SELECT setting::bigint * 8192 AS bytes FROM pg_settings WHERE name = 'shared_buffers';

2. 改 shared_buffers 必须重启,reload 一定无效。 它是 postmaster context。

bash
# 不推荐:以为 reload 能生效
pg_ctl reload -D /var/lib/postgresql/18/main   # shared_buffers 不会有任何变化

3. 别超过内存的 40%。 超过后通常不会更好,还会挤压操作系统页缓存。

ini
# 不推荐:128GB 机器给 96GB(75%)
shared_buffers = 96GB

4. 内存小于 1GB 时不要机械套用 25%。 要给操作系统留够内存。

ini
# 不推荐:512MB 内存的机器给 128MB
shared_buffers = 128MB
# 建议取更小比例,例如 64MB

5. 扩大 shared_buffers 不要忘记同步调大 max_wal_size。 否则 checkpoint 频率上升,写放大更明显。

ini
# 不推荐:只改了一个
shared_buffers = 8GB
max_wal_size = 1GB          # 相对太小,checkpoint 会被频繁触发

6. 把 shared_memory_type 改成 sysv 是常见错误。 官方不推荐,且会引入内核参数依赖。

ini
# 不推荐
shared_memory_type = sysv

7. huge_pages = on 在生产环境风险很高。 大页申请失败会让实例直接起不来。

ini
# 风险配置:OS 层大页没配好时直接启动失败
huge_pages = on
# 更稳妥的做法
huge_pages = try

8. 关联 pg_buffercache 与 pg_class 时忘记过滤数据库,会得到错误结果。

sql
-- 错误!没有限定 reldatabase,可能关联到别的库的同名关系
SELECT count(*) FROM pg_buffercache b JOIN pg_class c ON b.relfilenode = c.relfilenode;

9. 高命中率不能证明缓存配置合理。 命中率高也可能是「反复读同一小批数据」,真正瓶颈在排序落盘。

sql
-- 不推荐:只看命中率就宣布缓存没问题
SELECT round(100.0*blks_hit/(blks_hit+blks_read),2) FROM pg_stat_database;
-- 还要同时看 temp_files / temp_bytes

10. pg_buffercache_evict* 系列只能用于测试,禁止在生产使用。 官方在文档中明确说明它们是仅供开发者测试的。

sql
-- 严禁在生产执行!
SELECT pg_buffercache_evict_all();

11. 不要用 UPDATE pg_settings 试图改 shared_buffers。 那是会话级操作,对 postmaster 参数无效且会误导自己。

sql
-- 错误!
UPDATE pg_settings SET setting = '8388608' WHERE name = 'shared_buffers';

12. 调整前必须先确认内核共享内存限制。 尤其在容器/受限环境中,可能会直接启动失败。

bash
# 调整前必做
ipcs -l | head -20
cat /proc/sys/kernel/shmmax

最佳实践 ​

  1. 以 25% 为起点,在 25%~40% 区间内做 A/B 验证,不要凭感觉调。
sql
-- 每次调整后,等业务跑满一轮再采集对照数据
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();
  1. 调整前记录基线(值 + 命中率 + 缓存摘要),调整后对比。
sql
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());
  1. 专机专用永远是前提。 同一台机器上跑 Redis、Elasticsearch 时,shared_buffers 的取值要相应下调。
text
内存规划示例(32GB 机器,PostgreSQL + 少量其他服务):
  shared_buffers        6GB
  操作系统页缓存          12GB
  其他服务                8GB
  预留                    6GB
  1. 配套调整 max_wal_size 与 checkpoint_completion_target。
ini
shared_buffers = 8GB
max_wal_size = 8GB
min_wal_size = 1GB
checkpoint_completion_target = 0.9
  1. 在 Linux 上考虑显式启用大页(huge_pages = try),并先在 OS 层配好 vm.nr_hugepages。
bash
# OS 层:按需设置大页数量(2MB 页,4GB shared_buffers 需要 2048 个大页)
sysctl -w vm.nr_hugepages=2048
  1. 把 pg_buffercache 作为常规观测手段纳入巡检。 它的 summary / usage_counts 两个函数开销极小。
sql
SELECT * FROM pg_buffercache_summary();
SELECT * FROM pg_buffercache_usage_counts() ORDER BY usage_count;
  1. 在容器化部署中显式设定内存上限,并让 shared_buffers 与 limit 匹配。
yaml
# 示例:Kubernetes 资源限制下,shared_buffers 不要超过 limit 的 25%~40%
resources:
  limits:
    memory: "8Gi"
# 对应 shared_buffers 取 2GB 左右
  1. 区分「数据库共享缓存」和「操作系统页缓存」的观测口径。 只盯 shared_buffers 会漏掉一半信息。
bash
# 看操作系统页缓存回收情况
cat /proc/vmstat | grep -E "pgpg|pswp"
  1. 避免在业务高峰期做需要重启的内存调整。 shared_buffers 改变会让缓存「冷启动」,短期内命中率骤降。
bash
# 好:低峰窗口
pg_ctl restart -D /var/lib/postgresql/18/main -m fast
  1. 缓存被一次性大查询污染时,优先优化 SQL,而不是无脑加内存。
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;
  1. 每次重启后都要复核 pending_restart 是否为 false,确保配置真的落地。
sql
SELECT name, setting FROM pg_settings WHERE pending_restart;
  1. 把 shared_buffers 的取值写进版本控制的配置模板,并在注释里写明取值依据。
ini
# 依据:32GB 专机,25% 法则取 8GB;命中率由 93% 提升至 98.7%
shared_buffers = 8GB

参考链接 ​