配置参数生效方式
改完配置文件,参数未必立刻生效——这是 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 |
改了一个参数
│
▼
┌──────────────────────┐
│ 查 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 |
sighup | reload(SIGHUP) | ❌ | log_min_duration_statement、effective_cache_size、autovacuum |
superuser-backend | 新连接建立时由超级用户设定 | ⚠️ 需超级用户 | session_preload_libraries |
backend | 后端进程启动时设定 | ❌ | 由连接来源决定(如某些日志参数) |
superuser | SET(需超级用户)或 reload | ✅ 需超级用户 | work_mem 的配置默认、maintenance_work_mem |
user | SET(任何用户)或 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 不是「重新读一遍所有配置然后全部应用」。它的真实流程是:
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 会变成什么」。
会话内想拿到新值,必须:
RESET work_mem; -- 回到 reset_val(即配置文件里的新值)
SHOW work_mem; -- 现在是新值了或者重连。这个是排查「reload 了但没变」的核心认知。
语法与用法
触发 reload 的四种方式
# 方式一: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 函数(需超级用户或 pg_signal_backend 权限)
SELECT pg_reload_conf(); pg_reload_conf
----------------
t
(1 row)返回 t 只表示信号已发出,不表示配置解析成功。必须再查 pg_file_settings 或日志确认。
触发重启的方式
# 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 | 立即中止,下次启动做崩溃恢复 | 避免,除非卡死 |
会话级设置的语法
-- 当前会话有效,会话结束失效
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;查询哪些改动尚未生效
-- 尚未重启生效的参数
SELECT name, setting, unit, pending_restart
FROM pg_settings
WHERE pending_restart
ORDER BY name; name | setting | unit | pending_restart
-------------------+----------+------+-----------------
max_connections | 300 | | t
shared_buffers | 2097152 | 8kB | t
(2 rows)-- 配置文件中解析失败或被覆盖的行
SELECT sourcefile, sourceline, name, setting, applied, error
FROM pg_file_settings
WHERE applied = false
ORDER BY sourcefile, sourceline;当前值 vs 配置文件值的对照
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'); 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,再决定动作:
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'; name | context | unit | setting | how_to_apply
----------------+------------+------+---------+---------------------
shared_buffers | postmaster | 8kB | 16384 | 必须重启实例
(1 row)统计各上下文下的参数数量,可以快速建立直觉:
SELECT context, count(*) AS param_count
FROM pg_settings
GROUP BY context
ORDER BY param_count DESC; 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:
SELECT sourcefile, sourceline, name, setting, applied, error
FROM pg_file_settings
WHERE error IS NOT NULL
ORDER BY sourcefile, sourceline; 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 为空):
SELECT sourcefile, sourceline, name, setting, error
FROM pg_file_settings
WHERE applied = false AND error IS NULL
ORDER BY sourcefile, sourceline; 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 后,日志里会出现明确记录:
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 后都应该看一眼日志。
# 查看最近的 reload 日志
grep -i "SIGHUP\|reloading\|cannot be changed without restarting" /var/log/postgresql/postgresql-18-main.log | tail -20四、pending_restart 的正确用法
pending_restart 是唯一可靠的「欠一次重启」指示器。把它纳入监控:
SELECT count(*) AS params_pending_restart,
string_agg(name, ', ' ORDER BY name) AS params
FROM pg_settings
WHERE pending_restart; params_pending_restart | params
------------------------+----------------------------------------------
2 | max_connections, shared_buffers
(1 row)注意:pending_restart 只在服务器运行期间有意义。刚启动时所有参数都从文件读取,不会有 pending。
pending_restart 与「会话内 SET」无关
pending_restart 只反映「配置文件的改动是否需要重启」,不反映会话内的 SET。所以即使某个会话把 shared_buffers 相关的东西改乱了,pending_restart 也不会变。
-- 会话内 SET 不会影响 pending_restart
SET work_mem = '512MB';
SELECT name, pending_restart FROM pg_settings WHERE name = 'work_mem'; -- f五、会话参数的「快照」行为
这是最容易让人困惑的一点。用一个完整实验说明:
-- 会话 A
SHOW work_mem; -- 16MB(来自配置文件的快照)在另一个会话执行 reload 并修改配置:
-- 会话 B:先改配置
ALTER SYSTEM SET work_mem = '32MB';
SELECT pg_reload_conf();回到会话 A:
-- 会话 A:当前值没变
SHOW work_mem; -- 依然是 16MB
SELECT setting, reset_val FROM pg_settings WHERE name = 'work_mem'; setting | reset_val
---------+-----------
16384 | 32768setting 还是 16MB,但 reset_val 已经是 32MB 了。执行 RESET:
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,改了立刻作用于所有连接(包括已存在的):
SHOW log_min_duration_statement; -- 1000(会话 A)-- 会话 B
ALTER SYSTEM SET log_min_duration_statement = 500;
SELECT pg_reload_conf();-- 会话 A:立刻变了
SHOW log_min_duration_statement; -- 500
SELECT setting, reset_val, source FROM pg_settings WHERE name = 'log_min_duration_statement'; setting | reset_val | source
---------+-----------+--------------------
500 | 500 | configuration filesetting 与 reset_val 都变了,因为它是全局参数。区分「全局参数」与「会话参数」的判据就是 context:sighup 是全局的,user/superuser 是会话级的。
七、用 ALTER SYSTEM 实现「不编辑文件」的调整
ALTER SYSTEM 自动写 postgresql.auto.conf 并触发 reload(对 sighup 级参数立即生效):
-- 修改
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 不能在事务块或函数内执行:
BEGIN;
ALTER SYSTEM SET work_mem = '32MB';
-- ERROR: ALTER SYSTEM cannot run inside a transaction block
ROLLBACK;八、SET LOCAL 的事务级隔离
SET LOCAL 的作用域是当前事务,这是最安全的临时调参方式:
BEGIN;
SET LOCAL work_mem = '512MB'; -- 只在本次事务内有效
SET LOCAL enable_seqscan = off; -- 只影响本事务的计划
EXPLAIN (ANALYZE) SELECT ...; -- 大查询
COMMIT; -- 自动还原事务外使用 SET LOCAL 会给出警告,并只作用于当前事务(即立即失效):
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 的值保留 |
BEGIN;
SET LOCAL work_mem = '512MB'; -- 事务内有效
SET work_mem = '64MB'; -- 覆盖 LOCAL,且事务结束后仍保留
COMMIT;
SHOW work_mem; -- 64MB九、角色级与数据库级设置的生效时机
ALTER ROLE etl_worker SET work_mem = '256MB';
ALTER DATABASE analytics SET jit = off;这两类设置存储在系统目录,在新会话建立时应用。已存在的会话不会改变:
SELECT rolname, rolconfig FROM pg_roles WHERE rolconfig IS NOT NULL; rolname | rolconfig
-------------+--------------------------
etl_worker | {work_mem=256MB}
(1 row)SELECT datname, datconfig FROM pg_database WHERE datconfig IS NOT NULL; datname | datconfig
------------+-------------------
analytics | {jit=off}
(1 row)验证时必须新建连接:
psql -U etl_worker -d myapp -c "SHOW work_mem;"
# 256MB十、重启的实际影响与最小化
重启是唯一能让 postmaster 级参数生效的方式,但它会:
- 断开所有客户端连接;
- 触发一次检查点(shutdown checkpoint);
- 中断所有正在执行的查询;
- 需要重新预热缓冲池(性能短期下降)。
减少影响的做法:
# 1. 重启前先做一个检查点,缩短停机时间
psql -c "CHECKPOINT;"
# 2. 用 fast 模式重启(不等待长事务)
pg_ctl restart -D $PGDATA -m fast -w -t 60监控重启后是否发生崩溃恢复(crash recovery)——如果发生了,说明是 immediate 停止或异常宕机:
SELECT pg_postmaster_start_time() AS start_time,
now() - pg_postmaster_start_time() AS uptime; start_time | uptime
------------------------------+---------------
2026-09-29 10:25:00.123 CST | 00:00:12.345
(1 row)-- 检查是否需要崩溃恢复(PG15+ 不直接暴露,可看日志)
-- 日志中若出现 "database system was not properly shut down; automatic recovery in progress" 即为崩溃恢复十一、pg_ctl reload vs pg_reload_conf 的差别
| 方式 | 需要什么 | 返回什么 | 能否远程 |
|---|---|---|---|
pg_ctl reload | 文件系统访问 + PGDATA | 命令退出码 | ❌ |
systemctl reload | root / sudo | 命令退出码 | ❌ |
kill -HUP | 读 postmaster.pid 的权限 | 无 | ❌ |
SELECT pg_reload_conf() | 数据库连接 + 权限 | boolean | ✅ |
自动化运维(如配置管理工具)推荐用 pg_reload_conf(),因为它只需要一个数据库连接,不需要登录到主机:
psql -h db.example.com -U postgres -c "SELECT pg_reload_conf();"十二、SIGHUP 被忽略的特殊情况
若 postmaster 启动时带了 -c name=value 或通过 PGOPTIONS 传入参数,这些参数会被锁定为「命令行来源」,reload 无法覆盖它们:
# 启动时硬编码
postgres -D $PGDATA -c work_mem=32MBSELECT name, setting, source, sourcefile
FROM pg_settings
WHERE name = 'work_mem'; 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。
第一阶段:确认两者的生效方式。
SELECT name, context, setting, unit, pending_restart
FROM pg_settings
WHERE name IN ('work_mem', 'effective_cache_size'); 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。
ALTER SYSTEM SET effective_cache_size = '24GB';
SELECT pg_reload_conf(); pg_reload_conf
----------------
t
(1 row)立刻核验(这个参数全局生效,不需要重连):
SHOW effective_cache_size;
SELECT name, setting, reset_val, source, sourcefile
FROM pg_settings WHERE name = 'effective_cache_size'; 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——先在会话内验证效果。
不动全局配置,先在一个会话里单独放大,避免影响其他业务:
-- 在报表会话中
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; 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 万行。这里应记录日志证据:
-- 临时打开临时文件日志(会话级)
SET log_temp_files = 0;日志(在服务端日志中):
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 不宜大幅提升(每个排序/哈希节点都会用),所以:
-- 全局只从 4MB 提到 8MB(温和)
ALTER SYSTEM SET work_mem = '8MB';
SELECT pg_reload_conf();-- 给报表角色单独放大(只影响该角色的新会话)
ALTER ROLE report_ro SET work_mem = '128MB';
ALTER ROLE report_ro SET hash_mem_multiplier = 4.0;
-- 给某条具体查询用 SET LOCAL(最精准)第五阶段:核验各层次的最终状态。
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; 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的新会话中生效)。
用新连接验证角色级设置:
$ 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)角色级设置生效,符合预期。
第六阶段:确认无需重启。
SELECT count(*) FROM pg_settings WHERE pending_restart; count
-------
0
(1 row)零个待重启参数,说明本次调整全程无停机,达成目标。
注意事项
不要以为 reload 会中断连接。 这正是 reload 存在的意义:它不中断连接,只让 postmaster 重新读文件。
# 安全:不影响在线连接
pg_ctl reload -D $PGDATA
# 破坏性:会断开所有连接
pg_ctl restart -D $PGDATA不要在存在长事务时用 -m smart 重启。 它会一直等到长事务结束,可能造成「重启命令挂着不动」的假象。
# 错误!可能长时间卡住
pg_ctl stop -D $PGDATA -m smart
# 正确
pg_ctl restart -D $PGDATA -m fast不要用 -m immediate 做常规重启。 它会导致下次启动执行崩溃恢复(crash recovery),增加启动时间并可能引发数据一致性问题。
# 不推荐!非必要不用
pg_ctl stop -D $PGDATA -m immediate不要忘记 reload 后检查日志。 语法错误会被静默忽略,只有日志里才有痕迹。
# 错误做法:reload 后不看日志
pg_ctl reload -D $PGDATA && echo "done" # "done" 不代表配置生效了不要在事务块里执行 ALTER SYSTEM。 会直接报错。
-- 错误!
BEGIN;
ALTER SYSTEM SET work_mem = '64MB';
-- ERROR: ALTER SYSTEM cannot run inside a transaction block不要以为改了配置文件,已有连接就会变。 会话参数有快照行为。
-- 反例:reload 前建立的连接,reload 后 work_mem 不会变
SHOW work_mem; -- 还是旧值
-- 必须 RESET 或重连
RESET work_mem;不要把 shared_buffers 之类的参数改了文件就以为生效了。 它需要重启。
-- 查看是否欠重启
SELECT name, pending_restart FROM pg_settings WHERE name = 'shared_buffers';不要用 postgres -c 覆盖生产环境的关键参数。 命令行来源的参数 reload 无法覆盖,会成为「隐形锁定」。
# 危险:work_mem 从此改不动了
postgres -D $PGDATA -c work_mem=32MB不要在业务高峰重启。 重启会让缓冲池变冷,且所有连接需要重建,连接风暴可能打满 max_connections。
-- 重启前评估连接数
SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend';不要用 RESET ALL 在长连接池的连接上。 它会清掉连接池设置的会话参数(如 search_path、application_name 相关设置),可能导致后续查询行为异常。
-- 危险!会清掉所有会话级设置
RESET ALL;
-- 更安全:只重置目标参数
RESET work_mem;不要忘了 pg_reload_conf() 需要权限。 普通角色执行会失败。
-- 普通用户执行会报错
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 不是原子的。 若配置中有部分错误,正确解析的参数会生效,错误的行被忽略——会产生「配置处于半生效状态」的结果。所以必须预先校验。
-- 每次 reload 前的固定检查
SELECT count(*) AS err_cnt FROM pg_file_settings WHERE error IS NOT NULL;最佳实践
把「先预检、再 reload、后核验」固化为三步脚本。 让变更流程不依赖人肉记忆。
#!/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 判断生效方式,而不是靠记忆。 每个参数都可以实时查到。
SELECT name, context FROM pg_settings WHERE name = '<参数名>';把 pending_restart 纳入监控并告警。 「配置与实际不一致」是隐性的配置漂移,越早发现越好。
-- 监控探针
SELECT count(*) AS pending FROM pg_settings WHERE pending_restart;优先用 ALTER SYSTEM,而不是手工编辑文件。 它可回滚、可审计、自动 reload。
ALTER SYSTEM SET log_min_duration_statement = 500;
-- 回滚
ALTER SYSTEM RESET log_min_duration_statement;临时调参一律用 SET LOCAL 并放在事务里。 作用域可控,绝不留残留。
BEGIN;
SET LOCAL work_mem = '512MB';
-- ... 大查询 ...
COMMIT;需要长期差异化的会话参数,用角色级设置而不是全局放大。 例如批处理角色、报表角色。
ALTER ROLE etl_worker SET work_mem = '256MB';
ALTER ROLE report_ro SET statement_timeout = '60s';改 postmaster 级参数时,把重启安排进维护窗口,并提前缓存预热。 重启后第一次查询会较慢。
# 重启前先做检查点,缩短停机
psql -c "CHECKPOINT;"
sudo systemctl restart postgresql@18-main
# 重启后预热常用表
psql -c "SELECT count(*) FROM big_table;" >/dev/null用 pg_ctl reload 而不是 kill -HUP,避免 PID 读错。 脚本里尤其重要。
# 不推荐(PID 文件可能过期)
kill -HUP $(cat $PGDATA/postmaster.pid | head -1)
# 推荐
pg_ctl reload -D $PGDATA每次 reload 后主动 grep 日志确认。 把「WARNING: parameter ... cannot be changed without restarting」找出来。
grep -E "SIGHUP|reloading|cannot be changed" /var/log/postgresql/postgresql-18-main.log | tail -20用 reset_val 与 setting 的差异快速定位「会话被 SET 过」的参数。 这是排查「同一查询在两处行为不同」的第一招。
SELECT name, setting, reset_val, source
FROM pg_settings
WHERE setting <> reset_val
ORDER BY name;自动化环境用 SELECT pg_reload_conf(),不要依赖主机登录。 配置管理工具只需要数据库连接即可。
psql -h $DB_HOST -U postgres -c "SELECT pg_reload_conf();"重启前确认没有关键长事务与复制延迟风险。 主库重启会短暂中断复制。
-- 检查长事务
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;把配置变更与重启记录写入变更日志。 参数改了、重启了、验证了什么,全部留痕,是半年后排查问题的唯一线索。
-- 变更后立刻快照
\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 能完成的绝不重启,重启批攒到维护窗口一次做完。
-- 分类待变更参数
SELECT name, context, pending_restart FROM pg_settings WHERE source <> 'default' ORDER BY context, name;下一节
继续学习:ALTER SYSTEM 动态配置
参考链接
- PostgreSQL 官方文档:Setting Parameters(config-setting)
- PostgreSQL 官方文档:The pg_settings View(view-pg-settings)
- PostgreSQL 官方文档:The pg_file_settings View(view-pg-file-settings)
- PostgreSQL 官方文档:Server Signaling(pg-ctl)
- PostgreSQL 官方文档:System Administration Functions(functions-admin)
- PostgreSQL 官方文档:Managing Databases / Server Shutdown
- PostgreSQL 官方文档:Resource Consumption(runtime-config-resource)
- PostgreSQL 官方文档:Logging(runtime-config-logging)