Skip to content

其它内存参数 ​

PostgreSQL 的内存参数里,shared_buffers、work_mem、maintenance_work_mem 是「大三样」,其余十来个参数单个都很小、平时几乎不用动——但它们的默认值并非总是最优,某些高并发或特定负载下会成为瓶颈。本节把它们分门别类讲清楚:本地缓冲区(每会话)、共享缓冲小项(全局)、大页与共享内存实现、以及并行/栈相关的边界参数。

前置知识

阅读本节前,建议先了解:maintenance_work_mem 维护内存

基础概念 ​

这些参数该分几类 ​

text
┌───────────────────────────────────────────────────────────────────┐
│                    PostgreSQL 内存参数全景                          │
├───────────────────────────────────────────────────────────────────┤
│  ① 共享内存区(整个实例一份)          ← shared_buffers 已单独讲     │
│     ├─ shared_buffers                                              │
│     ├─ WAL 缓冲区:wal_buffers                                      │
│     ├─ 各类小缓冲:commit_timestamp / multixact / notify / ...      │
│     └─ 大页与共享内存实现:huge_pages / shared_memory_type          │
│                                                                    │
│  ② 每个后端进程的本地内存(每会话一份)                              │
│     ├─ work_mem(已讲)                                             │
│     ├─ maintenance_work_mem(已讲)                                 │
│     ├─ temp_buffers          ← 本节                                 │
│     └─ max_stack_depth       ← 本节                                 │
│                                                                    │
│  ③ 非内存但常一起调的配置                                            │
│     ├─ max_prepared_transactions(预留槽位)                         │
│     └─ max_connections(会话数上限,参与内存估算)                    │
└───────────────────────────────────────────────────────────────────┘

一张表看清默认值 ​

sql
SELECT name, setting, unit, context, boot_val
FROM pg_settings
WHERE name IN (
  'wal_buffers', 'temp_buffers', 'max_stack_depth', 'max_prepared_transactions',
  'commit_timestamp_buffers', 'multixact_member_buffers',
  'multixact_offset_buffers', 'notify_buffers', 'serializable_buffers',
  'subtransaction_buffers', 'transaction_buffers',
  'huge_pages', 'huge_page_size', 'shared_memory_type',
  'dynamic_shared_memory_type'
)
ORDER BY name;
text
             name             | setting | unit |   context   | boot_val
------------------------------+---------+------+-------------+----------
 commit_timestamp_buffers     | 0       | 8kB  | postmaster  | 0
 dynamic_shared_memory_type   | posix   |      | postmaster  | posix
 huge_page_size               | 0       |      | postmaster  | 0
 huge_pages                   | try     |      | postmaster  | try
 max_prepared_transactions    | 0       |      | postmaster  | 0
 max_stack_depth              | 2048    | kB   | superuser   | 2048
 multixact_member_buffers     | 32      | 8kB  | postmaster  | 32
 multixact_offset_buffers     | 16      | 8kB  | postmaster  | 16
 notify_buffers               | 16      | 8kB  | postmaster  | 16
 serializable_buffers         | 32      | 8kB  | postmaster  | 32
 shared_memory_type           | mmap    |      | postmaster  | mmap
 subtransaction_buffers       | 0       | 8kB  | postmaster  | 0
 temp_buffers                 | 1024    | 8kB  | user        | 1024
 transaction_buffers          | 0       | 8kB  | postmaster  | 0
 wal_buffers                  | -1      | 8kB  | postmaster  | -1
(15 rows)

注意单位是「块(8kB)」的参数

上表中带 unit = 8kB 的参数,setting 返回的是块数,不是字节数。例如 temp_buffers = 1024 表示 1024 × 8kB = 8MB。这与 work_mem(单位 kB)的规则不同。

「0 表示自动」的约定 ​

三个参数的默认值是 0,含义不是「关闭」,而是按 shared_buffers 自动推导:

text
commit_timestamp_buffers  = 0
subtransaction_buffers    = 0
transaction_buffers       = 0

默认含义:shared_buffers / 512,上限 1024 块,下限 16 块
sql
-- 例如 shared_buffers = 128MB = 16384 块
-- 16384 / 512 = 32 块 → 在 [16, 1024] 范围内 → 取 32 块 = 256kB
sql
-- 直接读实际分配情况
SELECT name, setting, unit FROM pg_settings
WHERE name IN ('commit_timestamp_buffers','subtransaction_buffers','transaction_buffers');
text
           name            | setting | unit
---------------------------+---------+------
 commit_timestamp_buffers  | 32      | 8kB
 subtransaction_buffers    | 32      | 8kB
 transaction_buffers       | 32      | 8kB
(3 rows)

与「自动清理」的 0 要区分

autovacuum_work_mem = -1 表示「跟随 maintenance_work_mem」;wal_buffers = -1 表示「按 shared_buffers 的 1/32 自动推导」;而这里的 0 表示「按 shared_buffers 自动推导(/512,16~1024 块)」。三者语义都不同,别混淆。

语法与用法 ​

本地缓冲区(每会话) ​

ini
# postgresql.conf
temp_buffers = '8MB'          # 默认值,每个会话访问临时表用
sql
-- 会话内可调(但必须在首次使用临时表之前)
SET temp_buffers = '64MB';

共享缓冲小项(需重启) ​

ini
# postgresql.conf —— 这些都是 postmaster 上下文,改完必须重启
wal_buffers = -1                  # -1 = 自动(shared_buffers/32,64kB ~ 一个 WAL 段)
commit_timestamp_buffers = 0      # 0 = 自动(shared_buffers/512,16~1024 块)
multixact_member_buffers = 32
multixact_offset_buffers = 16
notify_buffers = 16
serializable_buffers = 32
subtransaction_buffers = 0
transaction_buffers = 0

大页与共享内存实现(需重启) ​

ini
# postgresql.conf
huge_pages = try                  # try(默认)/ on / off
huge_page_size = 0                # 0 = 用系统默认大页大小
shared_memory_type = mmap         # mmap(默认)/ sysv / windows
dynamic_shared_memory_type = posix

会话与栈 ​

sql
-- 当前栈深度上限
SHOW max_stack_depth;
text
 max_stack_depth
-----------------
 2MB
sql
-- 查内核真实栈限制
bash
ulimit -s
text
8192
text
内核限制 8192kB = 8MB,PostgreSQL 默认 2MB,留有安全余量。

预备事务槽位 ​

ini
# postgresql.conf —— postmaster 上下文
max_prepared_transactions = 0     # 0 = 关闭两阶段提交
sql
-- 若要用两阶段提交,需设为 >= max_connections
-- max_prepared_transactions = 200

与相关命令的对照 ​

sql
-- 查看所有「内存相关」参数的当前值
SELECT name, setting, unit, context, source
FROM pg_settings
WHERE category ILIKE '%memory%' OR name IN (
  'temp_buffers','max_stack_depth','huge_pages','shared_memory_type',
  'dynamic_shared_memory_type','max_prepared_transactions'
)
ORDER BY name;
sql
-- 查看哪些参数改动后需要重启
SELECT name, setting, unit, context
FROM pg_settings
WHERE context = 'postmaster' AND name IN (
  'shared_buffers','wal_buffers','huge_pages','huge_page_size','shared_memory_type',
  'dynamic_shared_memory_type','max_prepared_transactions',
  'commit_timestamp_buffers','multixact_member_buffers','multixact_offset_buffers',
  'notify_buffers','serializable_buffers','subtransaction_buffers','transaction_buffers'
)
ORDER BY name;

详细说明 ​

一、temp_buffers:临时表页缓存 ​

temp_buffers 是每个会话用于访问临时表(temporary table)的本地缓冲区,默认 8MB。

sql
SELECT name, setting, unit, context FROM pg_settings WHERE name = 'temp_buffers';
text
     name     | setting | unit | context
--------------+---------+------+---------
 temp_buffers | 1024    | 8kB  | user
(1 row)
text
特点:
  · 只服务于「临时表的页访问」
  · 每个会话独享一份(不是共享内存)
  · 按需分配 —— 只在真正用到时占内存
  · 每个 buffer 描述符约 64 字节,实际用到一页才额外占 8kB
sql
-- 创建并使用临时表
BEGIN;
SET temp_buffers = '64MB';
CREATE TEMP TABLE t_stage (id int, val text);
INSERT INTO t_stage SELECT g, md5(g::text) FROM generate_series(1, 100000) g;
SELECT count(*) FROM t_stage;
COMMIT;

temp_buffers 只在首次使用临时表之前生效

官方规定:temp_buffers「可以在单个会话中更改,但只能在该会话首次使用临时表之前更改;之后尝试更改该值将对该会话无效」。

sql
-- 错误!此时已经用过临时表,改不生效
CREATE TEMP TABLE t1 (id int);
SET temp_buffers = '64MB';   -- 无效
sql
-- 正确顺序:先设置,再用临时表
SET temp_buffers = '64MB';
CREATE TEMP TABLE t1 (id int);

内存占用是「按需」的

把 temp_buffers 设大是廉价的:每个 buffer 描述符只占约 64 字节。只有当某个 buffer 真被使用时,才会额外消耗 8kB(BLCKSZ)。所以对于「可能偶尔用大临时表」的会话,设大一点没有坏处。

二、commit_timestamp_buffers:提交时间戳缓存 ​

PG15 引入。缓存 pg_commit_ts 目录的内容(提交时间戳)。

sql
SELECT name, setting, unit, context, short_desc
FROM pg_settings WHERE name = 'commit_timestamp_buffers';
text
           name            | setting | unit |  context   |              short_desc
---------------------------+---------+------+------------+--------------------------------------
 commit_timestamp_buffers  | 0       | 8kB  | postmaster | Sets the number of dedicated shared buffers for transaction commit timestamps.
(1 row)
text
默认 0 = 自动:shared_buffers / 512,上限 1024 块,下限 16 块
只在启用了 track_commit_timestamp 时才真正有用
sql
-- 查看是否启用了提交时间戳跟踪
SHOW track_commit_timestamp;
text
 track_commit_timestamp
------------------------
 off
(1 row)

三、multixact 相关缓冲 ​

「多事务(multixact)」是同一条行记录被多个事务同时加锁(如 SELECT FOR SHARE 多会话并发)时使用的结构。它有两个缓存参数:

参数默认值缓存对象
multixact_member_buffers32pg_multixact/members
multixact_offset_buffers16pg_multixact/offsets
sql
SELECT name, setting, unit FROM pg_settings
WHERE name IN ('multixact_member_buffers', 'multixact_offset_buffers');
text
            name            | setting | unit
----------------------------+---------+------
 multixact_member_buffers   | 32      | 8kB
 multixact_offset_buffers   | 16      | 8kB
(2 rows)
text
高并发、大量 FOR SHARE / FOR UPDATE 共享锁的场景,
multixact 增长快,可适当调大这两个值。
sql
-- 查看 multixact 的积压情况
SELECT * FROM pg_stat_activity WHERE wait_event_type = 'LWLock'
  AND wait_event ILIKE '%multixact%';

四、notify_buffers:LISTEN/NOTIFY 缓存 ​

缓存 pg_notify 目录,服务于 LISTEN / NOTIFY 机制。

sql
SELECT name, setting, unit FROM pg_settings WHERE name = 'notify_buffers';
text
      name       | setting | unit
-----------------+---------+------
 notify_buffers  | 16      | 8kB
(1 row)
sql
-- LISTEN/NOTIFY 使用示例
LISTEN order_events;

-- 另一个会话
NOTIFY order_events, 'order:12345:paid';
text
如果有大量 LISTEN/NOTIFY 消息(如应用内事件总线),
可适当调大 notify_buffers(需重启)。

五、serializable_buffers:可串行化事务缓存 ​

缓存 pg_serial 目录,服务于 SERIALIZABLE 隔离级别下的冲突检测。

sql
SELECT name, setting, unit FROM pg_settings WHERE name = 'serializable_buffers';
text
        name          | setting | unit
----------------------+---------+------
 serializable_buffers | 32      | 8kB
(1 row)
sql
-- 使用可串行化隔离级别
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT ...;
COMMIT;

用了 SERIALIZABLE 才需要关注

只有大量使用 SERIALIZABLE 隔离级别(如金融场景、需要强一致性的业务)时,这个参数才可能成为瓶颈。

六、subtransaction_buffers 与 transaction_buffers ​

这两个缓存分别服务于子事务(pg_subtrans)和事务提交状态(pg_xact)。

sql
SELECT name, setting, unit FROM pg_settings
WHERE name IN ('subtransaction_buffers', 'transaction_buffers');
text
          name           | setting | unit
-------------------------+---------+------
 subtransaction_buffers  | 32      | 8kB
 transaction_buffers     | 32      | 8kB
(2 rows)
text
默认 0 = 自动:shared_buffers / 512,上限 1024 块,下限 16 块

大量保存点(SAVEPOINT)会加剧子事务压力

应用层如果频繁使用 SAVEPOINT / ROLLBACK TO SAVEPOINT(ORM 常见做法),子事务数量会快速增长。PG15 引入 subtransaction_buffers 正是为了缓解这类场景的 I/O 压力。

sql
-- 子事务的压力可以从统计里观察到
SELECT name, setting FROM pg_settings WHERE name = 'subtransaction_buffers';
sql
-- 查看子事务历史的最大数量(间接反映压力)
SELECT * FROM pg_stat_database WHERE datname = current_database();

七、huge_pages:大页 ​

大页能减少页表项、降低 CPU 在内存管理上的开销,对使用大 shared_buffers 的实例效果明显。

sql
SELECT name, setting, context FROM pg_settings WHERE name = 'huge_pages';
text
    name     | setting |  context
-------------+---------+-----------
 huge_pages  | try     | postmaster
(1 row)
取值含义
try(默认)尝试申请大页,失败则回退到普通页
on必须使用大页,申请失败会导致实例启动失败
off不申请大页
ini
# postgresql.conf
huge_pages = try
huge_page_size = 0        # 0 = 用系统默认大页大小

try 与 on 的取舍

  • try:安全,但你可能一直没真正用上大页,还以为是生效的。
  • on:保证生效,但大页配置不足时实例直接起不来——这是生产事故的常见来源。

正确做法:先在测试环境用 on 验证大页配置充足,再用到生产;或保持 try 并通过 huge_pages_status 确认实际状态。

sql
-- 查看大页的实际使用状态(PG14+)
SELECT name, setting FROM pg_settings WHERE name = 'huge_pages_status';
text
       name        | setting
-------------------+---------
 huge_pages_status | off
(1 row)
text
可能取值:on / off / unknown
(unknown 表示平台不支持或未启用)
bash
# 查看系统大页配置(Linux)
grep -i huge /proc/meminfo
text
AnonHugePages:         0 kB
HugePages_Total:    2048
HugePages_Free:     2048
Hugepagesize:       2048 kB

平台限制

官方说明:huge_pages 目前仅在 Linux 和 Windows 上支持。在 Linux 上,只有 shared_memory_type = mmap(默认)时才支持。其他系统上设为 try 会被忽略。

八、huge_page_size ​

sql
SELECT name, setting, context FROM pg_settings WHERE name = 'huge_page_size';
text
     name      | setting |  context
---------------+---------+-----------
 huge_page_size| 0       | postmaster
(1 row)
text
0 = 使用系统默认大页大小

常见大页大小:
  Intel / AMD  : 2MB、1GB
  IBM POWER    : 16MB、16GB
  ARM          : 64kB、2MB、32MB、1GB

非默认值目前仅 Linux 支持

官方说明:huge_page_size 的非默认设置目前仅在 Linux 上支持。其他平台不要乱设。

九、shared_memory_type 与 dynamic_shared_memory_type ​

sql
SELECT name, setting, context FROM pg_settings
WHERE name IN ('shared_memory_type', 'dynamic_shared_memory_type');
text
            name            | setting |  context
----------------------------+---------+-----------
 dynamic_shared_memory_type | posix   | postmaster
 shared_memory_type         | mmap    | postmaster
(2 rows)
参数取值说明
shared_memory_typemmap(多数平台默认)用 mmap 分配匿名共享内存
sysv用 shmget 分配 System V 共享内存(不推荐)
windowsWindows 共享内存
dynamic_shared_memory_typeposix用 shm_open 分配 POSIX 共享内存
sysvSystem V 共享内存
windowsWindows

sysv 不推荐

官方明确说明:sysv 选项「在任何平台上都不是默认值,通常不推荐使用,因为它通常需要非默认的内核设置才能支持大额分配」。

并行查询依赖动态共享内存

并行查询的工作进程之间通过动态共享内存通信。如果 dynamic_shared_memory_type 配置有问题,并行查询会退化甚至报错。

sql
-- 确认动态共享内存后端在运行
SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'parallel worker';

十、max_stack_depth:执行栈上限 ​

sql
SELECT name, setting, unit, context FROM pg_settings WHERE name = 'max_stack_depth';
text
       name       | setting | unit |  context
------------------+---------+------+-----------
 max_stack_depth  | 2048    | kB   | superuser
(1 row)
text
默认 2MB,对应内核栈限制减去约 1MB 安全余量。

为什么需要余量:
  服务器并非在每个例程里都检查栈深度,
  只在「可能递归」的关键例程里检查。
  因此必须留出不能精确检查的部分所需的空间。
bash
# 查看内核栈限制
ulimit -s
text
8192
text
内核 8MB,PostgreSQL 默认 2MB(保守),余量充足。

若复杂 PL/pgSQL 函数报「stack depth limit exceeded」,
可在不超过内核限制的前提下调大 max_stack_depth。
sql
-- 调大(只影响本会话)
SET max_stack_depth = '6MB';

设置超过内核限制会崩溃

官方警告:把 max_stack_depth 设得高于实际内核限制,会让失控的递归函数直接压垮后端进程(crash)。在不提供内核限制信息的平台上,服务器无法阻止你设成不安全的值。

十一、max_prepared_transactions:预备事务槽位 ​

sql
SELECT name, setting, context FROM pg_settings WHERE name = 'max_prepared_transactions';
text
           name            | setting | context
---------------------------+---------+-----------
 max_prepared_transactions | 0       | postmaster
(1 row)
text
0(默认)= 完全关闭两阶段提交(PREPARE TRANSACTION)

若使用两阶段提交(如分布式事务、某些中间件):
  建议 >= max_connections
  让每个会话都可能有预备事务挂起
sql
-- 查看当前是否有预备事务
SELECT gid, prepared, owner, database FROM pg_prepared_xacts;

备库上必须 >= 主库

官方规定:运行备库(standby)时,max_prepared_transactions 必须设为与主库相同或更高的值,否则备库上将不允许执行查询。

ini
# 主库
max_prepared_transactions = 200
# 备库(同值或更高)
max_prepared_transactions = 200

十二、wal_buffers:WAL 缓冲区 ​

WAL(Write-Ahead Log,预写式日志)在写入磁盘之前,先在共享内存里攒一批。wal_buffers 就是这块共享内存的大小。

sql
SELECT name, setting, unit, context, boot_val FROM pg_settings WHERE name = 'wal_buffers';
text
    name     | setting | unit |  context   | boot_val
-------------+---------+------+------------+----------
 wal_buffers | -1      | 8kB  | postmaster | -1
(1 row)
text
默认值 -1 = 自动选择:shared_buffers / 32(约 3%)
          下限 64kB
          上限「一个 WAL 段」的大小,通常 16MB
sql
-- 例:shared_buffers = 128MB = 16384 块
-- 16384 / 32 = 512 块 = 4MB → 在 [64kB, 16MB] 内 → 取 4MB
规则说明
-1(默认)自动 = shared_buffers 的 1/32,不小于 64kB、不超过一个 WAL 段(通常 16MB)
正值且 < 32kB一律按 32kB 处理
不带单位按 WAL 块理解,即 XLOG_BLCKSZ 字节,通常 8kB
生效时机只能 server start(postmaster 上下文)
ini
# postgresql.conf —— 必须重启
wal_buffers = '16MB'

WAL 缓冲的内容「每次提交就写出去」

官方说明:WAL 缓冲的内容在每次事务提交时就写到磁盘,所以把它设得极大收益有限。但在「很多客户端同时提交」的繁忙服务器上,设为至少几 MB 可以提升写入性能。默认的 -1 自动取值在多数场景下已经是合理结果。

别把它当成「写缓存」来优化

wal_buffers 不是「攒得越多、写盘越少」的写缓存——它不能推迟持久化。真正的批量提交优化手段是 commit_delay / commit_siblings、以及把 synchronous_commit 调低。

sql
-- 核对实际分配(pg_shmem_allocations 里名为 "XLOG Buffers")
SELECT name, pg_size_pretty(size) AS size
FROM pg_shmem_allocations WHERE name = 'XLOG Buffers';
text
     name      |  size
---------------+---------
 XLOG Buffers  | 4194304
(1 row)
text
4194304 字节 = 4MB,与 shared_buffers = 128MB 时 的 1/32 相符。
text
调大 wal_buffers 的判断依据:
  · 观察 pg_stat_wal(PG14+)的 wal_buffers_full 是否持续增长
  · 若 wal_buffers_full 增长明显 → 可加大
sql
-- PG14+:WAL 活动统计
SELECT wal_records, wal_fpi, wal_bytes, wal_buffers_full, stats_reset
FROM pg_stat_wal;
text
 wal_records | wal_fpi | wal_bytes  | wal_buffers_full |         stats_reset
-------------+---------+------------+------------------+-------------------------------
     4821003 |  120984 | 1284531200 |              421 | 2026-09-01 00:00:12.334521+08
(1 row)
text
wal_buffers_full > 0 且持续增长 = 缓冲被写满、需要等待刷盘 → 可考虑调大
但即便是繁忙系统,一般也不需要超过 16MB(一个 WAL 段)

十三、内存参数与并发会话的估算 ​

所有「每会话」参数都要乘以并发数,做容量规划时必须一起算。

text
总内存需求 ≈ 共享内存(固定)
           + Σ 每会话内存 × 活跃会话数
           + 操作系统预留

共享内存 = shared_buffers
         + 各类小缓冲(commit_timestamp / multixact / notify / ...)
         + WAL 缓冲(wal_buffers)
         + 锁表等其它结构

每会话内存 = work_mem × 并发操作数
           + temp_buffers(仅用临时表时)
           + 其它小结构
sql
-- 查看共享内存实际分配明细(PG14+)
SELECT * FROM pg_shmem_allocations ORDER BY size DESC LIMIT 15;
text
             name              |    off    |   size    | allocated_size
-------------------------------+-----------+-----------+----------------
 Buffer Blocks                 |   1048576 | 134217728 |      134217728
 XLOG Buffers                  |           |   4194304 |        4194304
 ...
(15 rows)

pg_shmem_allocations 是核对共享内存的好工具

它能列出每块共享内存结构的实际大小,帮你验证「shared_buffers + 各小缓冲」的真实占用,排查「共享内存比预期大」的问题。

实战示例 ​

场景一:大量 LISTEN/NOTIFY 导致等待 ​

sql
-- 步骤 1:观察是否有 notify 相关等待
SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity
WHERE wait_event IS NOT NULL
GROUP BY 1, 2
ORDER BY 3 DESC;
text
 wait_event_type |   wait_event   | count
-----------------+----------------+-------
 LWLock          | NotifyQueueLock|    12
(1 row)
text
多个会话在 NotifyQueueLock 上等待 → notify 缓冲可能偏小。
sql
-- 步骤 2:确认当前配置
SHOW notify_buffers;
text
 notify_buffers
----------------
 16
sql
-- 步骤 3:调大(需重启)
ALTER SYSTEM SET notify_buffers = 128;
sql
-- 步骤 4:确认需要重启
SELECT name, setting, pending_restart
FROM pg_settings WHERE name = 'notify_buffers';
text
      name       | setting | pending_restart
-----------------+---------+-----------------
 notify_buffers  | 128     | t
(1 row)
bash
# 步骤 5:在维护窗口重启
pg_ctl restart -D /var/lib/postgresql/18/main
sql
-- 步骤 6:核验
SELECT name, setting, pending_restart FROM pg_settings WHERE name = 'notify_buffers';
text
      name       | setting | pending_restart
-----------------+---------+-----------------
 notify_buffers  | 128     | f
(1 row)

场景二:PL/pgSQL 递归函数报栈深度超限 ​

sql
-- 步骤 1:复现错误
CREATE OR REPLACE FUNCTION count_down(n int) RETURNS int
LANGUAGE plpgsql AS $$
BEGIN
  IF n <= 0 THEN
    RETURN 0;
  END IF;
  RETURN count_down(n - 1) + 1;
END;
$$;

SELECT count_down(500000);
text
ERROR:  stack depth limit exceeded
HINT:  Increase the configuration parameter "max_stack_depth" (currently 2MB), after ensuring the platform's stack depth limit is adequate.
bash
# 步骤 2:先确认真实内核限制
ulimit -s
text
8192
text
内核 8MB → 可安全设到约 7MB(留 1MB 余量)
sql
-- 步骤 3:会话内临时调大(不重启)
SET max_stack_depth = '6MB';
SELECT count_down(500000);
text
 count_down
------------
     500000
(1 row)
sql
-- 步骤 4:若确为常态需求,全局固化
ALTER SYSTEM SET max_stack_depth = '6MB';
SELECT pg_reload_conf();

max_stack_depth 不要超过内核限制

只设到「内核限制 - 1MB」以内。设过头会导致递归失控时整个后端进程崩溃。

场景三:大 shared_buffers 实例启用大页 ​

bash
# 步骤 1:查看当前大页配置
grep -i huge /proc/meminfo
text
HugePages_Total:       0
HugePages_Free:        0
Hugepagesize:       2048 kB
sql
-- 步骤 2:确认 PostgreSQL 当前未用上大页
SELECT name, setting FROM pg_settings WHERE name = 'huge_pages_status';
text
       name        | setting
-------------------+---------
 huge_pages_status | off
(1 row)
text
shared_buffers = 32GB,未用大页 → 页表开销可观,值得启用。
bash
# 步骤 3:计算需要多少大页
# 32GB shared_buffers,2MB 大页 → 约 16384 页,多留余量取 17000
bash
# 步骤 4:临时配置大页(测试)
sysctl -w vm.nr_hugepages=17000
grep -i huge /proc/meminfo
text
HugePages_Total:   17000
HugePages_Free:    17000
Hugepagesize:       2048 kB
ini
# 步骤 5:先在 postgresql.conf 用 try 验证
huge_pages = try
bash
# 重启
pg_ctl restart -D /var/lib/postgresql/18/main
sql
-- 步骤 6:确认真正用上了大页
SELECT name, setting FROM pg_settings WHERE name = 'huge_pages_status';
text
       name        | setting
-------------------+---------
 huge_pages_status | on
(1 row)
bash
# 步骤 7:确认稳定后,写进 sysctl.conf 持久化
echo 'vm.nr_hugepages=17000' >> /etc/sysctl.conf
ini
# 步骤 8:若追求「必须生效」,改为 on(大页不足会启动失败,需确保配置充足)
huge_pages = on

on 之前务必确认大页充足

改 on 之后若大页数量不足,PostgreSQL 会直接启动失败。生产环境变更前,先确认 HugePages_Free 足够,并准备好回滚方案。

场景四:32GB 内存服务器的完整参数配置 ​

把前面几节讲的参数串起来,给一台 32GB 内存、专用跑 PostgreSQL 18 的服务器写一份可落地的配置。假设负载是「中等并发 OLTP + 每日报表」,max_connections = 200。

text
内存预算(32GB 物理内存):
  shared_buffers          8GB    (25% 法则)
  操作系统页缓存          约 16GB(把 effective_cache_size 设为它)
  work_mem 峰值余量       约 3GB
  maintenance_work_mem    1~2GB (按需,非长期占用)
  wal_buffers             16MB
  各小缓冲合计            < 10MB
  预留余量                若干
ini
# ============================================================
# postgresql.conf —— PostgreSQL 18,32GB 内存专用服务器
# ============================================================

# ---------- 共享内存(postmaster,需重启) ----------
shared_buffers = '8GB'            # 25% × 32GB
huge_pages = try                  # 大页:先 try,确认 huge_pages_status = on
huge_page_size = 0                # 0 = 系统默认(Linux 通常 2MB)
shared_memory_type = mmap         # Linux 默认,且大页仅在 mmap 下可用
dynamic_shared_memory_type = posix

wal_buffers = -1                  # 自动 = shared_buffers/32 = 256MB?
                                  # 注意上限是一个 WAL 段(通常 16MB)
                                  # 所以实际取 16MB;明确写 wal_buffers = '16MB' 亦可

# ---------- PG18 小缓冲(postmaster,需重启) ----------
commit_timestamp_buffers = 0      # 自动 = shared_buffers/512,16~1024 块
subtransaction_buffers = 0        # 自动
transaction_buffers = 0           # 自动
multixact_member_buffers = 32     # 默认
multixact_offset_buffers = 16     # 默认
notify_buffers = 16               # 默认;LISTEN/NOTIFY 密集时可加大
serializable_buffers = 32         # 默认;SERIALIZABLE 事务密集时可加大

# ---------- 查询执行内存(user,reload 生效) ----------
work_mem = '8MB'                  # OLTP 保守值;报表角色另用 ALTER ROLE 放大
hash_mem_multiplier = 2.0         # 默认;哈希操作额度 = work_mem × 2

# ---------- 维护内存(reload 生效) ----------
maintenance_work_mem = '1GB'      # 手动 VACUUM / CREATE INDEX
autovacuum_work_mem = '256MB'     # 与上面拆开,防止 autovacuum 放大

# ---------- 优化器内存估算(user,reload 生效) ----------
effective_cache_size = '24GB'     # 约 75% 内存(含 shared_buffers)

# ---------- 会话内存 ----------
temp_buffers = '16MB'             # 默认 8MB;有临时表场景可放大
max_stack_depth = '6MB'           # 内核 ulimit -s 若为 8MB,留 2MB 余量

# ---------- 非内存但常一起调 ----------
max_connections = 200
max_prepared_transactions = 0     # 不用两阶段提交就保持 0
sql
-- 应用后逐项核对(分两批:reload 批 + 重启批)
SELECT name, setting, unit, context, source, pending_restart
FROM pg_settings
WHERE name IN (
  'shared_buffers','wal_buffers','temp_buffers','max_stack_depth',
  'work_mem','hash_mem_multiplier','maintenance_work_mem','autovacuum_work_mem',
  'effective_cache_size','commit_timestamp_buffers','transaction_buffers',
  'subtransaction_buffers','multixact_member_buffers','multixact_offset_buffers',
  'notify_buffers','serializable_buffers','huge_pages','huge_pages_status',
  'shared_memory_type','dynamic_shared_memory_type','max_prepared_transactions'
)
ORDER BY context, name;
text
 -- 关键点:pending_restart = t 的参数必须在维护窗口重启才生效
sql
-- 重启后确认大页真的用上了
SELECT name, setting FROM pg_settings WHERE name = 'huge_pages_status';
text
       name        | setting
-------------------+---------
 huge_pages_status | on
(1 row)
sql
-- 核对共享内存真实占用(Buffer Blocks 应约为 8GB)
SELECT name, pg_size_pretty(size) AS size
FROM pg_shmem_allocations
WHERE name IN ('Buffer Blocks', 'XLOG Buffers')
ORDER BY size DESC;
text
      name      |  size
----------------+---------
 Buffer Blocks  | 8192 MB
 XLOG Buffers   | 16 MB
(2 rows)
sql
-- 最后确认工作内存类的实际值(用户会话视角)
SELECT name, setting, unit FROM pg_settings
WHERE name IN ('work_mem','maintenance_work_mem','effective_cache_size','temp_buffers')
ORDER BY name;
text
         name          | setting | unit
-----------------------+---------+------
 effective_cache_size  | 3145728 | 8kB
 maintenance_work_mem  | 1048576 | kB
 temp_buffers          | 2048    | 8kB
 work_mem              | 8192    | kB
(4 rows)

这份配置不是「抄了就好」

上面每个数字都依赖具体的并发量、查询形态与硬件。请按本站各节的判读方法(EXPLAIN 的 Sort Method / Batches、pg_stat_wal 的 wal_buffers_full、pg_shmem_allocations、huge_pages_status)验证后再固化,不要照搬。

注意事项 ​

不要把 0 理解成「关闭」。 commit_timestamp_buffers = 0 等表示按 shared_buffers 自动推导,不是禁用。

sql
-- 0 = 自动(shared_buffers/512,16~1024 块)
SELECT name, setting, unit FROM pg_settings
WHERE name IN ('commit_timestamp_buffers','transaction_buffers','subtransaction_buffers');

不要忽略这些参数的单位是「块」而非「kB」。 与 work_mem 的规则不同。

sql
-- temp_buffers = 1024 → 1024 × 8kB = 8MB,不是 1024kB
SELECT name, setting * 8192 AS bytes FROM pg_settings WHERE name = 'temp_buffers';

不要以为改完这些参数不用重启。 上表里绝大多数小缓冲和内存类型参数都是 postmaster 上下文。

sql
-- 检查是否有待重启的改动
SELECT name, setting, pending_restart FROM pg_settings WHERE pending_restart;

不要在 huge_pages = on 且大页不足时重启。 会导致实例起不来。

ini
# 危险:大页不足时直接启动失败
huge_pages = on
# 稳妥:先用 try 观察 huge_pages_status
huge_pages = try

不要把 max_stack_depth 设得超过内核栈限制。

bash
ulimit -s
sql
-- 设成「内核限制 - 1MB」以内
ALTER SYSTEM SET max_stack_depth = '6MB';

不要试图在用过临时表之后再 SET temp_buffers。 该会话内已失效。

sql
-- 错误!
CREATE TEMP TABLE t1 (id int);
SET temp_buffers = '64MB';   -- 无效

不要在只有少量共享缓冲的实例上纠结 multixact/notify 缓冲。 这些项很小,通常不是瓶颈。

text
32 块 × 8kB = 256kB,量级极小
先优化 shared_buffers / work_mem,再回头看这些

不要把 wal_buffers 当成「写缓存」来调大,也不要混淆三个 -1 与 0 的语义。

sql
-- 判断是否真的需要调大:看 wal_buffers_full 是否持续增长(PG14+)
SELECT wal_buffers_full FROM pg_stat_wal;
text
wal_buffers = -1                 → 自动 = shared_buffers/32(64kB ~ 16MB)
autovacuum_work_mem = -1         → 跟随 maintenance_work_mem
commit_timestamp_buffers = 0     → 自动 = shared_buffers/512(16 ~ 1024 块)

不要忽略备库上 max_prepared_transactions 必须 >= 主库。 否则备库不允许查询。

sql
-- 核验主备一致
SHOW max_prepared_transactions;

不要在未确认平台支持的情况下调 huge_page_size。 非默认值目前仅 Linux 支持。

sql
-- 保持默认 0(用系统默认大页大小)最安全
SELECT name, setting FROM pg_settings WHERE name = 'huge_page_size';

不要把 shared_memory_type 改成 sysv。 官方不推荐,且通常需要非默认内核设置。

ini
# 保持默认
shared_memory_type = mmap

不要忘记动态共享内存对并行查询的影响。 配置异常会导致并行退化。

sql
-- 确认并行工作进程能起来
EXPLAIN (ANALYZE) SELECT ... ;  -- 观察是否有 Parallel 节点

不要只算 shared_buffers 就以为算清了共享内存。 还有各小缓冲、WAL 缓冲、锁表等。

sql
-- 看真实明细
SELECT * FROM pg_shmem_allocations ORDER BY size DESC LIMIT 20;

最佳实践 ​

先优化「大三样」(shared_buffers、work_mem、maintenance_work_mem),再考虑这些细节参数。

sql
SELECT name, setting, unit FROM pg_settings
WHERE name IN ('shared_buffers','work_mem','maintenance_work_mem','effective_cache_size')
ORDER BY name;

temp_buffers 对「可能用大临时表」的会话可适当设大,成本很低。

ini
# 每个 buffer 描述符仅约 64 字节,实际用到才占 8kB
temp_buffers = '8MB'

用 pg_shmem_allocations 核对共享内存真实占用,做容量规划。

sql
SELECT name, pg_size_pretty(size) AS size, pg_size_pretty(allocated_size) AS allocated
FROM pg_shmem_allocations ORDER BY size DESC LIMIT 20;

wal_buffers 一般保持默认 -1;仅当 wal_buffers_full 持续增长时才显式设为几 MB ~ 16MB。

ini
# 默认即自动:shared_buffers/32,64kB ~ 一个 WAL 段
wal_buffers = -1
sql
-- 依据一:WAL 缓冲写满的次数(PG14+)
SELECT wal_buffers_full FROM pg_stat_wal;
-- 依据二:共享内存里 XLOG Buffers 的实际大小
SELECT name, pg_size_pretty(size) FROM pg_shmem_allocations WHERE name = 'XLOG Buffers';

大 shared_buffers 实例启用大页,并先用 try 验证。

ini
huge_pages = try
huge_page_size = 0
sql
SELECT name, setting FROM pg_settings WHERE name = 'huge_pages_status';

huge_pages = on 前务必确认大页充足,并准备回滚方案。

bash
grep -i huge /proc/meminfo

max_stack_depth 设为「内核栈限制 - 1MB」以内,且仅在确有需要时调。

bash
ulimit -s
ini
# 内核 8MB → 设 6MB
max_stack_depth = '6MB'

使用两阶段提交时,max_prepared_transactions 设为 >= max_connections,并保证主备一致。

ini
max_prepared_transactions = 200

大量 LISTEN/NOTIFY 或 SERIALIZABLE 事务的场景,适当调大对应的专用缓冲。

ini
notify_buffers = 128
serializable_buffers = 64

保持 shared_memory_type = mmap 与 dynamic_shared_memory_type = posix(Linux 默认)。

ini
shared_memory_type = mmap
dynamic_shared_memory_type = posix

把所有「需重启」的内存参数汇总,在维护窗口统一变更。

sql
SELECT name, setting, context FROM pg_settings
WHERE context = 'postmaster' AND category ILIKE '%memory%'
ORDER BY name;

把这些参数纳入配置版本库,并注明「需重启」。

ini
# postgresql.conf(纳入 git)
# ---- 需重启生效 ----
huge_pages = try
shared_memory_type = mmap
max_prepared_transactions = 0
# ---- 会话级 ----
temp_buffers = '8MB'

变更后用 pending_restart 确认状态,避免「以为生效了其实没有」。

sql
SELECT name, setting, pending_restart FROM pg_settings WHERE pending_restart;

下一节

继续学习:EXPLAIN 基础

参考链接 ​