Skip to content

配置参数生效方式 ​

改完配置文件,参数未必立刻生效——这是 PostgreSQL 运维中最容易踩的坑。参数生效方式取决于它的上下文(context):有的发一个 SIGHUP 就生效,有的必须重启整个实例,有的甚至只能在编译期定死。本节把「改了什么、要怎么才能生效、怎么确认生效了」这条链路完整讲清楚,并给出可直接使用的运维流程。

前置知识

阅读本节前,建议先了解:pg_ident.conf 用户映射

基础概念 ​

三种生效层次 ​

层次触发方式影响范围是否中断连接典型参数
重启(restart)停止再启动实例整个实例是shared_buffers、max_connections、wal_level
重载(reload)SIGHUP 信号整个实例否log_min_duration_statement、effective_cache_size
会话级(SET)SQL SET当前会话/事务否work_mem、search_path
text
                    改了一个参数
                         │
                         ▼
              ┌──────────────────────┐
              │ 查 pg_settings.context│
              └──────────┬───────────┘
                         │
     ┌───────────┬───────┴────────┬────────────┬──────────────┐
     ▼           ▼                ▼            ▼              ▼
 internal   postmaster        sighup      superuser/     user
     │           │                │         backend          │
     ▼           ▼                ▼            ▼              ▼
  改不了     必须重启          reload 即可   SET / 连接时     SET 即可
             实例              发 SIGHUP     设定          (任何人)

七类 context 与生效方式对照 ​

context生效方式能否在会话内 SET示例
internal不可修改(编译期固定)❌block_size、wal_block_size、segment_size
postmaster重启实例❌shared_buffers、max_connections、wal_level、listen_addresses
sighupreload(SIGHUP)❌log_min_duration_statement、effective_cache_size、autovacuum
superuser-backend新连接建立时由超级用户设定⚠️ 需超级用户session_preload_libraries
backend后端进程启动时设定❌由连接来源决定(如某些日志参数)
superuserSET(需超级用户)或 reload✅ 需超级用户work_mem 的配置默认、maintenance_work_mem
userSET(任何用户)或 reload✅ 任何用户search_path、statement_timeout

注意 work_mem 的双重身份

work_mem 的 context 是 user——意思是任何用户都可以为自己的会话 SET。但它在 postgresql.conf 中的值作为「新会话的默认值」,改配置文件后需要 reload 才对新会话生效(已有会话不受影响,因为值在会话建立时已复制)。

这是「改了配置,老连接没变」这一现象的根本原因:很多参数的值是在会话建立时快照的。

三种「改配置」的手段对比 ​

手段持久化作用范围是否需 reload适用场景
编辑 postgresql.conf✅ 文件全部会话是(sighup 级)长期基线配置
ALTER SYSTEM SET✅ postgresql.auto.conf全部会话自动触发 reload线上快速调整、可回滚
SET / SET LOCAL❌ 会话结束即失效当前会话/事务不需要临时救火、单会话调优
ALTER ROLE ... SET✅ 系统目录该角色的会话新会话生效批处理/报表角色专属参数
ALTER DATABASE ... SET✅ 系统目录该库的会话新会话生效库级别差异

reload 到底做了什么 ​

reload 不是「重新读一遍所有配置然后全部应用」。它的真实流程是:

text
pg_ctl reload  /  SELECT pg_reload_conf()
        │
        ▼
  postmaster 收到 SIGHUP
        │
        ├── 重新解析 postgresql.conf / auto.conf
        ├── 校验语法(错误只记日志,不中断)
        ├── 对 sighup 级参数:立即应用
        ├── 对 postmaster 级参数:标记 pending_restart = true,但**不应用**
        └── 向所有 backend 发送信号,让它们重读配置文件
                │
                └── 各 backend 对「会话可设」参数:只更新 reset_val,
                    但**不改变当前会话已生效的值**

reload 不会让已存在的会话改变参数值

一个已有的连接,在 reload 之后 SHOW work_mem; 依然是旧值——因为它当前的值是从配置快照来的,reload 只更新了「下次 RESET 会变成什么」。

会话内想拿到新值,必须:

sql
RESET work_mem;   -- 回到 reset_val(即配置文件里的新值)
SHOW work_mem;    -- 现在是新值了

或者重连。这个是排查「reload 了但没变」的核心认知。

语法与用法 ​

触发 reload 的四种方式 ​

bash
# 方式一:pg_ctl(需知道 PGDATA)
pg_ctl reload -D /var/lib/postgresql/18/main

# 方式二:systemd
sudo systemctl reload postgresql
sudo systemctl reload postgresql@18-main

# 方式三:发信号(传统方式)
kill -HUP $(head -1 /var/lib/postgresql/18/main/postmaster.pid)
sql
-- 方式四:SQL 函数(需超级用户或 pg_signal_backend 权限)
SELECT pg_reload_conf();
text
 pg_reload_conf
----------------
 t
(1 row)

返回 t 只表示信号已发出,不表示配置解析成功。必须再查 pg_file_settings 或日志确认。

触发重启的方式 ​

bash
# systemd(推荐)
sudo systemctl restart postgresql@18-main

# pg_ctl
pg_ctl restart -D /var/lib/postgresql/18/main -m fast

# 优雅停止再启动
pg_ctl stop -D /var/lib/postgresql/18/main -m fast
pg_ctl start -D /var/lib/postgresql/18/main

停止模式(-m)的区别很重要:

模式行为适用
smart等所有客户端主动断开才停维护窗口、无长事务时
fast断开客户端、回滚事务、检查点后停常规重启(推荐)
immediate立即中止,下次启动做崩溃恢复避免,除非卡死

会话级设置的语法 ​

sql
-- 当前会话有效,会话结束失效
SET work_mem = '64MB';

-- 当前会话有效,但 SET 时不校验值(PG16+ 的 SET 默认会校验)
SET work_mem TO '64MB';

-- 只在当前事务内有效,事务提交/回滚后自动还原
BEGIN;
SET LOCAL work_mem = '256MB';
-- ... 大查询 ...
COMMIT;  -- work_mem 自动回到事务前的值

-- 恢复为「配置文件的当前值」
RESET work_mem;
RESET ALL;

查询哪些改动尚未生效 ​

sql
-- 尚未重启生效的参数
SELECT name, setting, unit, pending_restart
FROM pg_settings
WHERE pending_restart
ORDER BY name;
text
       name        | setting  | unit | pending_restart
-------------------+----------+------+-----------------
 max_connections   | 300      |      | t
 shared_buffers    | 2097152  | 8kB  | t
(2 rows)
sql
-- 配置文件中解析失败或被覆盖的行
SELECT sourcefile, sourceline, name, setting, applied, error
FROM pg_file_settings
WHERE applied = false
ORDER BY sourcefile, sourceline;

当前值 vs 配置文件值的对照 ​

sql
SELECT name,
       setting       AS current_value,
       reset_val     AS value_after_reset,
       boot_val      AS compiled_default,
       source,
       pending_restart
FROM pg_settings
WHERE name IN ('work_mem', 'shared_buffers', 'max_connections', 'log_min_duration_statement');
text
            name            | current_value | value_after_reset | compiled_default |       source       | pending_restart
----------------------------+---------------+-------------------+------------------+--------------------+-----------------
 log_min_duration_statement | 1000          | 1000              | -1               | configuration file | f
 max_connections            | 100               | 300               | 100              | configuration file | t
 shared_buffers             | 16384         | 2097152           | 16384            | configuration file | t
 work_mem                   | 32768         | 16384             | 4096             | session            | f

这张表信息量极大:

  • work_mem 的 source 是 session,说明当前会话被 SET 过,所以 current_value(32MB) ≠ value_after_reset(16MB)。
  • max_connections 与 shared_buffers 的 pending_restart = t,说明文件里已经是新值(value_after_reset),但内存里还是旧值(current_value),必须重启。
  • log_min_duration_statement 的 pending_restart = f,说明它已经生效了。

详细说明 ​

一、用 pg_settings.context 决定生效方式 ​

遇到任何参数,先查 context,再决定动作:

sql
SELECT name, context, unit, setting,
       CASE context
         WHEN 'internal'          THEN '不可修改(编译期固定)'
         WHEN 'postmaster'        THEN '必须重启实例'
         WHEN 'sighup'            THEN 'reload(SIGHUP)即可'
         WHEN 'superuser-backend' THEN '新连接时由超级用户设定'
         WHEN 'backend'           THEN '由连接来源决定'
         WHEN 'superuser'         THEN 'SET(需超级用户)或 reload'
         WHEN 'user'              THEN 'SET(任何用户)或 reload'
       END AS how_to_apply
FROM pg_settings
WHERE name = 'shared_buffers';
text
      name      |  context   | unit | setting |    how_to_apply
----------------+------------+------+---------+---------------------
 shared_buffers | postmaster | 8kB  | 16384   | 必须重启实例
(1 row)

统计各上下文下的参数数量,可以快速建立直觉:

sql
SELECT context, count(*) AS param_count
FROM pg_settings
GROUP BY context
ORDER BY param_count DESC;
text
     context      | param_count
------------------+-------------
 user             |         143
 sighup           |          82
 postmaster       |          51
 superuser        |          35
 internal         |          21
 superuser-backend|           6
 backend          |           2
(7 rows)

结论:绝大多数参数(user + sighup = 225 个)都能不重启就调整,真正需要重启的只有 51 个左右。所以「调优必须重启」是误解。

二、reload 前必做的预检 ​

直接 reload 的危险在于:语法错误不会导致失败,只会被静默忽略。所以先查 pg_file_settings:

sql
SELECT sourcefile, sourceline, name, setting, applied, error
FROM pg_file_settings
WHERE error IS NOT NULL
ORDER BY sourcefile, sourceline;
text
        sourcefile         | sourceline |         name          | setting | applied |              error
---------------------------+------------+-----------------------+---------+---------+-----------------------------------
 /etc/postgresql/18/main/postgresql.conf |  305 | log_min_duraton_statement | 1000 | f | unrecognized configuration parameter
(1 row)

error 非空就是有问题的行。必须为 0 行才 reload。

同时检查「被后续条目覆盖」的行(applied = f 但 error 为空):

sql
SELECT sourcefile, sourceline, name, setting, error
FROM pg_file_settings
WHERE applied = false AND error IS NULL
ORDER BY sourcefile, sourceline;
text
        sourcefile         | sourceline |     name      | setting |           error
---------------------------+------------+---------------+---------+---------------------------
 /etc/postgresql/18/main/postgresql.conf |  120 | shared_buffers | 4GB     | setting is overridden by a later entry
(1 row)

这类不是错误,但它明确告诉你「这一行是废的」。

三、reload 的确认与日志证据 ​

reload 后,日志里会出现明确记录:

text
2026-09-29 10:20:03.001 CST [12034] LOG:  received SIGHUP, reloading configuration files
2026-09-29 10:20:03.014 CST [12034] LOG:  parameter "work_mem" changed to "16384"
2026-09-29 10:20:03.014 CST [12034] LOG:  parameter "log_min_duration_statement" changed to "1000"
2026-09-29 10:20:03.015 CST [12034] WARNING:  parameter "shared_buffers" cannot be changed without restarting the server

第三行的 WARNING 就是「你以为 reload 了,实际没生效」的直接证据。每次 reload 后都应该看一眼日志。

bash
# 查看最近的 reload 日志
grep -i "SIGHUP\|reloading\|cannot be changed without restarting" /var/log/postgresql/postgresql-18-main.log | tail -20

四、pending_restart 的正确用法 ​

pending_restart 是唯一可靠的「欠一次重启」指示器。把它纳入监控:

sql
SELECT count(*) AS params_pending_restart,
       string_agg(name, ', ' ORDER BY name) AS params
FROM pg_settings
WHERE pending_restart;
text
 params_pending_restart |                    params
------------------------+----------------------------------------------
                      2 | max_connections, shared_buffers
(1 row)

注意:pending_restart 只在服务器运行期间有意义。刚启动时所有参数都从文件读取,不会有 pending。

pending_restart 与「会话内 SET」无关

pending_restart 只反映「配置文件的改动是否需要重启」,不反映会话内的 SET。所以即使某个会话把 shared_buffers 相关的东西改乱了,pending_restart 也不会变。

sql
-- 会话内 SET 不会影响 pending_restart
SET work_mem = '512MB';
SELECT name, pending_restart FROM pg_settings WHERE name = 'work_mem';  -- f

五、会话参数的「快照」行为 ​

这是最容易让人困惑的一点。用一个完整实验说明:

sql
-- 会话 A
SHOW work_mem;    -- 16MB(来自配置文件的快照)

在另一个会话执行 reload 并修改配置:

sql
-- 会话 B:先改配置
ALTER SYSTEM SET work_mem = '32MB';
SELECT pg_reload_conf();

回到会话 A:

sql
-- 会话 A:当前值没变
SHOW work_mem;                      -- 依然是 16MB
SELECT setting, reset_val FROM pg_settings WHERE name = 'work_mem';
text
 setting | reset_val
---------+-----------
 16384   | 32768

setting 还是 16MB,但 reset_val 已经是 32MB 了。执行 RESET:

sql
RESET work_mem;
SHOW work_mem;                      -- 32MB(reset_val)

结论:reload 更新的是 reset_val;setting 只有在 RESET 或新建会话时才会变成新值。

哪些参数有这种「快照」行为?主要是 user / superuser 上下文的参数(work_mem、maintenance_work_mem、search_path、statement_timeout 等)。而 sighup 级参数(如 log_min_duration_statement)是全局生效、不受会话快照影响的。

六、哪些 sighup 参数是真正「立即全局生效」的 ​

log_min_duration_statement 这类属于 sighup,改了立刻作用于所有连接(包括已存在的):

sql
SHOW log_min_duration_statement;    -- 1000(会话 A)
sql
-- 会话 B
ALTER SYSTEM SET log_min_duration_statement = 500;
SELECT pg_reload_conf();
sql
-- 会话 A:立刻变了
SHOW log_min_duration_statement;    -- 500
SELECT setting, reset_val, source FROM pg_settings WHERE name = 'log_min_duration_statement';
text
 setting | reset_val |       source
---------+-----------+--------------------
 500     | 500       | configuration file

setting 与 reset_val 都变了,因为它是全局参数。区分「全局参数」与「会话参数」的判据就是 context:sighup 是全局的,user/superuser 是会话级的。

七、用 ALTER SYSTEM 实现「不编辑文件」的调整 ​

ALTER SYSTEM 自动写 postgresql.auto.conf 并触发 reload(对 sighup 级参数立即生效):

sql
-- 修改
ALTER SYSTEM SET log_min_duration_statement = 500;
ALTER SYSTEM SET work_mem = '32MB';

-- 恢复默认(删除 auto.conf 中的条目)
ALTER SYSTEM RESET log_min_duration_statement;

-- 清空 auto.conf 中的所有条目
ALTER SYSTEM RESET ALL;

ALTER SYSTEM 不能设置所有参数

以下参数不能通过 ALTER SYSTEM 设置:

  • 由命令行或 postgresql.conf 排他控制的:data_directory
  • 用来控制自身行为的:allow_alter_system
  • 预设(preset)参数:如 block_size(internal 上下文)

且 ALTER SYSTEM 不能在事务块或函数内执行:

sql
BEGIN;
ALTER SYSTEM SET work_mem = '32MB';
-- ERROR:  ALTER SYSTEM cannot run inside a transaction block
ROLLBACK;

八、SET LOCAL 的事务级隔离 ​

SET LOCAL 的作用域是当前事务,这是最安全的临时调参方式:

sql
BEGIN;
SET LOCAL work_mem = '512MB';       -- 只在本次事务内有效
SET LOCAL enable_seqscan = off;     -- 只影响本事务的计划
EXPLAIN (ANALYZE) SELECT ...;       -- 大查询
COMMIT;                             -- 自动还原

事务外使用 SET LOCAL 会给出警告,并只作用于当前事务(即立即失效):

sql
SET LOCAL work_mem = '512MB';
-- WARNING:  SET LOCAL can only be used in transaction blocks
SHOW work_mem;   -- 未变化

SET LOCAL 的还原行为需要特别注意:

情况事务内 SET LOCAL事务后
COMMIT有效还原
ROLLBACK有效还原
事务内又 SET(非 LOCAL)LOCAL 被覆盖SET 的值保留
sql
BEGIN;
SET LOCAL work_mem = '512MB';   -- 事务内有效
SET work_mem = '64MB';          -- 覆盖 LOCAL,且事务结束后仍保留
COMMIT;
SHOW work_mem;                  -- 64MB

九、角色级与数据库级设置的生效时机 ​

sql
ALTER ROLE etl_worker SET work_mem = '256MB';
ALTER DATABASE analytics SET jit = off;

这两类设置存储在系统目录,在新会话建立时应用。已存在的会话不会改变:

sql
SELECT rolname, rolconfig FROM pg_roles WHERE rolconfig IS NOT NULL;
text
   rolname   |        rolconfig
-------------+--------------------------
 etl_worker  | {work_mem=256MB}
(1 row)
sql
SELECT datname, datconfig FROM pg_database WHERE datconfig IS NOT NULL;
text
  datname   |     datconfig
------------+-------------------
 analytics  | {jit=off}
(1 row)

验证时必须新建连接:

bash
psql -U etl_worker -d myapp -c "SHOW work_mem;"
# 256MB

十、重启的实际影响与最小化 ​

重启是唯一能让 postmaster 级参数生效的方式,但它会:

  1. 断开所有客户端连接;
  2. 触发一次检查点(shutdown checkpoint);
  3. 中断所有正在执行的查询;
  4. 需要重新预热缓冲池(性能短期下降)。

减少影响的做法:

bash
# 1. 重启前先做一个检查点,缩短停机时间
psql -c "CHECKPOINT;"

# 2. 用 fast 模式重启(不等待长事务)
pg_ctl restart -D $PGDATA -m fast -w -t 60

监控重启后是否发生崩溃恢复(crash recovery)——如果发生了,说明是 immediate 停止或异常宕机:

sql
SELECT pg_postmaster_start_time() AS start_time,
       now() - pg_postmaster_start_time() AS uptime;
text
          start_time          |    uptime
------------------------------+---------------
 2026-09-29 10:25:00.123 CST  | 00:00:12.345
(1 row)
sql
-- 检查是否需要崩溃恢复(PG15+ 不直接暴露,可看日志)
-- 日志中若出现 "database system was not properly shut down; automatic recovery in progress" 即为崩溃恢复

十一、pg_ctl reload vs pg_reload_conf 的差别 ​

方式需要什么返回什么能否远程
pg_ctl reload文件系统访问 + PGDATA命令退出码❌
systemctl reloadroot / sudo命令退出码❌
kill -HUP读 postmaster.pid 的权限无❌
SELECT pg_reload_conf()数据库连接 + 权限boolean✅

自动化运维(如配置管理工具)推荐用 pg_reload_conf(),因为它只需要一个数据库连接,不需要登录到主机:

bash
psql -h db.example.com -U postgres -c "SELECT pg_reload_conf();"

十二、SIGHUP 被忽略的特殊情况 ​

若 postmaster 启动时带了 -c name=value 或通过 PGOPTIONS 传入参数,这些参数会被锁定为「命令行来源」,reload 无法覆盖它们:

bash
# 启动时硬编码
postgres -D $PGDATA -c work_mem=32MB
sql
SELECT name, setting, source, sourcefile
FROM pg_settings
WHERE name = 'work_mem';
text
   name   | setting |       source        | sourcefile
----------+---------+---------------------+------------
 work_mem | 32768   | command line        | /usr/lib/postgresql/18/bin/postgres
(1 row)

source = command line 时,改配置文件+reload 不会有效果,必须重启并去掉命令行参数。这是排查「怎么改都没用」时的关键分支。

实战示例 ​

场景:线上调整 work_mem 与 effective_cache_size 并验证 ​

需求:某报表查询在做哈希连接时落盘(磁盘临时文件),需要临时放大 work_mem;同时优化器低估了可用缓存,要把 effective_cache_size 从默认 4GB 调到 24GB。

第一阶段:确认两者的生效方式。

sql
SELECT name, context, setting, unit, pending_restart
FROM pg_settings
WHERE name IN ('work_mem', 'effective_cache_size');
text
         name          |  context  | setting | unit | pending_restart
-----------------------+-----------+---------+------+-----------------
 effective_cache_size  | user      | 524288  | 8kB  | f
 work_mem              | user      | 4096    | kB   | f

结论:两者都是 user 上下文,不需要重启。effective_cache_size 只需要 reload(对全局规划生效);work_mem 需要 reload 后由新会话或 RESET 生效。

第二阶段:全局调整 effective_cache_size。

sql
ALTER SYSTEM SET effective_cache_size = '24GB';
SELECT pg_reload_conf();
text
 pg_reload_conf
----------------
 t
(1 row)

立刻核验(这个参数全局生效,不需要重连):

sql
SHOW effective_cache_size;
SELECT name, setting, reset_val, source, sourcefile
FROM pg_settings WHERE name = 'effective_cache_size';
text
      name            | setting | reset_val |       source       |                  sourcefile
----------------------+---------+-----------+--------------------+------------------------------------------------
 effective_cache_size | 3145728 | 3145728   | configuration file | /var/lib/postgresql/18/main/postgresql.auto.conf
(1 row)

3145728 × 8kB = 24GB,sourcefile 指向 postgresql.auto.conf,说明 ALTER SYSTEM 写入成功。

第三阶段:调整 work_mem——先在会话内验证效果。

不动全局配置,先在一个会话里单独放大,避免影响其他业务:

sql
-- 在报表会话中
BEGIN;
SET LOCAL work_mem = '256MB';
SET LOCAL log_temp_files = 0;   -- 顺带打开临时文件日志

EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT c.country, count(*) AS orders, sum(o.amount) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= '2026-01-01'
GROUP BY c.country
ORDER BY total DESC;
COMMIT;
text
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=185432.11..192871.35 rows=196 width=48) (actual time=1841.203..2145.877 rows=193 loops=1)
   Group Key: c.country
   ->  Sort  (cost=185432.11..186402.55 rows=388176 width=24) (actual time=1841.187..1980.442 rows=402113 loops=1)
         Sort Key: (sum(o.amount)) DESC
         Sort Method: external merge  Disk: 62184kB      ◀── 落盘了
         ->  Hash Join  (cost=12410.22..145301.90 rows=388176 width=24) (actual time=203.115..1443.201 rows=402113 loops=1)
               Hash Cond: (o.customer_id = c.id)
               ->  Index Scan using idx_orders_created_at on orders o  (cost=0.43..128112.55 rows=402113 width=20) (actual time=0.031..988.412 rows=402113 loops=1)
               ->  Hash  (cost=7621.11..7621.11 rows=387111 width=16) (actual time=198.442..198.442 rows=387110 loops=1)
                     Buckets: 524288  Batches: 1  Memory Usage: 22528kB
 Planning Time: 1.882 ms
 Execution Time: 2148.331 ms
(11 rows)

虽然设了 256MB,但 Sort Method: external merge Disk: 62184kB 说明排序仍然落盘——因为排序节点的 work_mem 上限是 256MB,但哈希节点用的是 work_mem × hash_mem_multiplier。这里排序是 Sort 节点(用 work_mem),62MB 落盘说明实际可用小于 62MB?不对——排序落盘 62MB 说明它需要的内存超过 256MB 的部分才落盘。

进一步定位:真正的问题是 Sort 节点在 GroupAggregate 之上,需要排序 40 万行。这里应记录日志证据:

sql
-- 临时打开临时文件日志(会话级)
SET log_temp_files = 0;

日志(在服务端日志中):

text
2026-09-29 10:31:07.442 CST [20188] LOG:  temporary file: path "base/pgsql_tmp/pgsql_tmp20188.0.sharedfileset/0.0", size 63676416
2026-09-29 10:31:07.442 CST [20188] STATEMENT:  SELECT c.country, count(*) FROM orders o JOIN customers c ...

第四阶段:改为全局小幅提升 + 针对批处理角色单独放大。

全局 work_mem 不宜大幅提升(每个排序/哈希节点都会用),所以:

sql
-- 全局只从 4MB 提到 8MB(温和)
ALTER SYSTEM SET work_mem = '8MB';
SELECT pg_reload_conf();
sql
-- 给报表角色单独放大(只影响该角色的新会话)
ALTER ROLE report_ro SET work_mem = '128MB';
ALTER ROLE report_ro SET hash_mem_multiplier = 4.0;

-- 给某条具体查询用 SET LOCAL(最精准)

第五阶段:核验各层次的最终状态。

sql
SELECT name, setting, reset_val, source, sourcefile, pending_restart
FROM pg_settings
WHERE name IN ('work_mem', 'effective_cache_size', 'hash_mem_multiplier')
ORDER BY name;
text
        name         | setting | reset_val |       source       |                  sourcefile                   | pending_restart
---------------------+---------+-----------+--------------------+-----------------------------------------------+-----------------
 effective_cache_size| 3145728 | 3145728   | configuration file | /var/lib/postgresql/18/main/postgresql.auto.conf | f
 hash_mem_multiplier | 2       | 2         | default            |                                               | f
 work_mem            | 32768   | 8192      | session            |                                               | f
(1 row)

结果解读:

  • effective_cache_size:setting = reset_val = 24GB,全局已生效。
  • work_mem:当前会话 source = session(被 SET 过 32MB),但 reset_val = 8MB,说明全局配置已改为 8MB,且会在新会话/RESET 后生效。
  • hash_mem_multiplier:在当前会话仍是默认 2(因为角色级设置只在 report_ro 的新会话中生效)。

用新连接验证角色级设置:

bash
$ psql -U report_ro -d analytics -c "SHOW work_mem; SHOW hash_mem_multiplier;"
 work_mem
----------
 128MB
(1 row)

 hash_mem_multiplier
---------------------
 4
(1 row)

角色级设置生效,符合预期。

第六阶段:确认无需重启。

sql
SELECT count(*) FROM pg_settings WHERE pending_restart;
text
 count
-------
     0
(1 row)

零个待重启参数,说明本次调整全程无停机,达成目标。

注意事项 ​

不要以为 reload 会中断连接。 这正是 reload 存在的意义:它不中断连接,只让 postmaster 重新读文件。

bash
# 安全:不影响在线连接
pg_ctl reload -D $PGDATA
# 破坏性:会断开所有连接
pg_ctl restart -D $PGDATA

不要在存在长事务时用 -m smart 重启。 它会一直等到长事务结束,可能造成「重启命令挂着不动」的假象。

bash
# 错误!可能长时间卡住
pg_ctl stop -D $PGDATA -m smart
# 正确
pg_ctl restart -D $PGDATA -m fast

不要用 -m immediate 做常规重启。 它会导致下次启动执行崩溃恢复(crash recovery),增加启动时间并可能引发数据一致性问题。

bash
# 不推荐!非必要不用
pg_ctl stop -D $PGDATA -m immediate

不要忘记 reload 后检查日志。 语法错误会被静默忽略,只有日志里才有痕迹。

bash
# 错误做法:reload 后不看日志
pg_ctl reload -D $PGDATA && echo "done"   # "done" 不代表配置生效了

不要在事务块里执行 ALTER SYSTEM。 会直接报错。

sql
-- 错误!
BEGIN;
ALTER SYSTEM SET work_mem = '64MB';
-- ERROR:  ALTER SYSTEM cannot run inside a transaction block

不要以为改了配置文件,已有连接就会变。 会话参数有快照行为。

sql
-- 反例:reload 前建立的连接,reload 后 work_mem 不会变
SHOW work_mem;    -- 还是旧值
-- 必须 RESET 或重连
RESET work_mem;

不要把 shared_buffers 之类的参数改了文件就以为生效了。 它需要重启。

sql
-- 查看是否欠重启
SELECT name, pending_restart FROM pg_settings WHERE name = 'shared_buffers';

不要用 postgres -c 覆盖生产环境的关键参数。 命令行来源的参数 reload 无法覆盖,会成为「隐形锁定」。

bash
# 危险:work_mem 从此改不动了
postgres -D $PGDATA -c work_mem=32MB

不要在业务高峰重启。 重启会让缓冲池变冷,且所有连接需要重建,连接风暴可能打满 max_connections。

sql
-- 重启前评估连接数
SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend';

不要用 RESET ALL 在长连接池的连接上。 它会清掉连接池设置的会话参数(如 search_path、application_name 相关设置),可能导致后续查询行为异常。

sql
-- 危险!会清掉所有会话级设置
RESET ALL;
-- 更安全:只重置目标参数
RESET work_mem;

不要忘了 pg_reload_conf() 需要权限。 普通角色执行会失败。

sql
-- 普通用户执行会报错
SELECT pg_reload_conf();
-- ERROR:  permission denied for function pg_reload_conf

需要超级用户或拥有相应角色(如 pg_signal_backend 不足以调用此函数;实际需要超级用户)。

Windows 上 HBA 变更的行为与其他平台不同。 Windows 没有 SIGHUP,pg_hba.conf 的改动由后端进程检查时立即读取,因此「reload 了」和「没 reload」在 Windows 上差别不明显,跨平台脚本不要依赖这个细节。

reload 不是原子的。 若配置中有部分错误,正确解析的参数会生效,错误的行被忽略——会产生「配置处于半生效状态」的结果。所以必须预先校验。

sql
-- 每次 reload 前的固定检查
SELECT count(*) AS err_cnt FROM pg_file_settings WHERE error IS NOT NULL;

最佳实践 ​

把「先预检、再 reload、后核验」固化为三步脚本。 让变更流程不依赖人肉记忆。

bash
#!/usr/bin/env bash
set -euo pipefail
PGDATA=${PGDATA:-/var/lib/postgresql/18/main}

echo "== 1. 预检配置 =="
errs=$(psql -Atc "SELECT count(*) FROM pg_file_settings WHERE error IS NOT NULL")
if [ "$errs" != "0" ]; then
  echo "配置有 $errs 处错误,中止:"
  psql -c "SELECT sourcefile, sourceline, name, error FROM pg_file_settings WHERE error IS NOT NULL"
  exit 1
fi

echo "== 2. reload =="
psql -c "SELECT pg_reload_conf()"

echo "== 3. 核验关键参数 =="
psql -c "SELECT name, setting, unit, source, pending_restart FROM pg_settings WHERE source <> 'default' ORDER BY name"

用 pg_settings 的 context 判断生效方式,而不是靠记忆。 每个参数都可以实时查到。

sql
SELECT name, context FROM pg_settings WHERE name = '<参数名>';

把 pending_restart 纳入监控并告警。 「配置与实际不一致」是隐性的配置漂移,越早发现越好。

sql
-- 监控探针
SELECT count(*) AS pending FROM pg_settings WHERE pending_restart;

优先用 ALTER SYSTEM,而不是手工编辑文件。 它可回滚、可审计、自动 reload。

sql
ALTER SYSTEM SET log_min_duration_statement = 500;
-- 回滚
ALTER SYSTEM RESET log_min_duration_statement;

临时调参一律用 SET LOCAL 并放在事务里。 作用域可控,绝不留残留。

sql
BEGIN;
SET LOCAL work_mem = '512MB';
-- ... 大查询 ...
COMMIT;

需要长期差异化的会话参数,用角色级设置而不是全局放大。 例如批处理角色、报表角色。

sql
ALTER ROLE etl_worker SET work_mem = '256MB';
ALTER ROLE report_ro SET statement_timeout = '60s';

改 postmaster 级参数时,把重启安排进维护窗口,并提前缓存预热。 重启后第一次查询会较慢。

bash
# 重启前先做检查点,缩短停机
psql -c "CHECKPOINT;"
sudo systemctl restart postgresql@18-main
# 重启后预热常用表
psql -c "SELECT count(*) FROM big_table;" >/dev/null

用 pg_ctl reload 而不是 kill -HUP,避免 PID 读错。 脚本里尤其重要。

bash
# 不推荐(PID 文件可能过期)
kill -HUP $(cat $PGDATA/postmaster.pid | head -1)
# 推荐
pg_ctl reload -D $PGDATA

每次 reload 后主动 grep 日志确认。 把「WARNING: parameter ... cannot be changed without restarting」找出来。

bash
grep -E "SIGHUP|reloading|cannot be changed" /var/log/postgresql/postgresql-18-main.log | tail -20

用 reset_val 与 setting 的差异快速定位「会话被 SET 过」的参数。 这是排查「同一查询在两处行为不同」的第一招。

sql
SELECT name, setting, reset_val, source
FROM pg_settings
WHERE setting <> reset_val
ORDER BY name;

自动化环境用 SELECT pg_reload_conf(),不要依赖主机登录。 配置管理工具只需要数据库连接即可。

bash
psql -h $DB_HOST -U postgres -c "SELECT pg_reload_conf();"

重启前确认没有关键长事务与复制延迟风险。 主库重启会短暂中断复制。

sql
-- 检查长事务
SELECT pid, now() - xact_start AS xact_age, state, query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL AND now() - xact_start > interval '5 minutes'
ORDER BY xact_age DESC;

-- 检查复制状态
SELECT client_addr, state, sync_state, replay_lag FROM pg_stat_replication;

把配置变更与重启记录写入变更日志。 参数改了、重启了、验证了什么,全部留痕,是半年后排查问题的唯一线索。

sql
-- 变更后立刻快照
\copy (SELECT name, setting, unit, source FROM pg_settings WHERE source <> 'default' ORDER BY name) TO 'config-after-change.csv' WITH CSV HEADER

尽量把变更拆成「先 reload 类,后重启类」两批。 reload 能完成的绝不重启,重启批攒到维护窗口一次做完。

sql
-- 分类待变更参数
SELECT name, context, pending_restart FROM pg_settings WHERE source <> 'default' ORDER BY context, name;

下一节

继续学习:ALTER SYSTEM 动态配置

参考链接 ​