ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PostgreSQL启停命令全解析:入口、停机信号与reload排错

PostgreSQL启停命令全解析:入口、停机信号与reload排错

1. 三套启停入口,先搞清楚你在跟谁打交道

PostgreSQL 启动停止命令这几个字,乍一看是最没技术含量的部分——pg_ctl start、pg_ctl stop、systemctl restart postgresql,翻来覆去就这几条,谁不会敲。真正把人从床上叫起来的,恰恰是这几条命令:敲了启动,回车之后报FATAL: lock file "postmaster.pid" already exists;敲了停止,命令挂在那里几分钟不动;重启完了应用那边一片超时,监控告警刷屏。问题不在于命令本身,而在于大多数人只记住了「怎么敲」,没搞清楚「敲下去之后谁在干活」。

这篇内容我按实际运维时的手感来拆:一台 PostgreSQL 实例的启停到底有几个入口,停止命令背后发出去的是什么信号,启动那几秒数据库内部在忙什么,改完配置该 reload 还是 restart,重启之后为什么性能会掉一截,以及停不下来、起不来的时候按什么顺序排查。适合刚开始接手数据库的研发、负责日常运维的同学,也适合已经能敲命令但总在细节上翻车的朋友,我会把每个「为什么这么做」讲透。

1.1 systemctl、pg_ctl、pg_ctlcluster,别混着用

PostgreSQL 没有 MySQL 那一套mysqld_safe守护进程,它的启停入口取决于你当初是怎么装的。常见三种情况,对应的命令完全不一样:

安装方式常用启停入口特点
源码编译 / 部分通用二进制包pg_ctl -D $PGDATA start/stop/restart直接对着数据目录操作,最底层
RHEL、CentOS 系 RPM 包systemctl start/stop/restart postgresql-14一个 unit 管一个实例
Debian、Ubuntu 系 deb 包pg_ctlcluster 14 main start/stop/restart一个postgresql.service管机器上所有集群

先说你手头这套属于哪一种。判断方法很简单,看一眼进程树就够了:

ps -ef | grep postgres | grep -v grep

如果进程的父进程是systemd(1),那它归 systemd 管,你就老老实实用systemctl。如果父进程是另一个postgres进程,那个就是 postmaster 本身,说明它是被pg_ctl直接拉起来的。

为什么强调「别混着用」?这是我在实际环境里见过最多的低级事故。有人用systemctl stop postgresql-14停了服务,systemd 记录这个 unit 处于 inactive 状态,然后他又习惯性地用pg_ctl -D /var/lib/pgsql/14/data start把库拉起来了。此时数据库在跑,但 systemd 依然认为服务是停的。下次机器重启、或者有人执行systemctl restart postgresql-14,systemd 会尝试启动一个「已经占用了 5432 端口」的新实例,直接失败。更麻烦的是,很多监控系统是按 systemd 的 unit 状态来判断实例死活的,你觉得库好好的,监控那边一片红。

我自己的规矩是:谁负责拉起这个进程,就只用谁的入口去停它。systemd 拉起来的就用systemctl,pg_ctl拉起来的就用pg_ctl,Debian 系用pg_ctlcluster。真要换管理方式,先把原方式停干净,确认端口释放了,再用新方式起。

Debian 系还有个容易忽略的点:pg_lsclusters能看到机器上所有集群的状态,包括版本、端口、状态、日志路径。同一台机器跑 13 和 15 两个集群是常规操作,你要是systemctl restart postgresql,它会把所有集群一起重启,而不是你只想动的那一个。这种时候必须用pg_ctlcluster 15 main restart精确打击。

1.2 一个数据目录就是一个实例,postmaster.pid是它的身份证

pg_ctl的所有操作都离不开-D后面那个数据目录,因为 PostgreSQL 的规矩是「一个数据目录对应一个 postmaster 进程」,没有例外。数据目录里有个postmaster.pid文件,很多人报错时看到它,但从来没打开看过。它的内容一般是这样几行:

8241 /var/lib/postgresql/14/main 1723456789 5432 /var/run/postgresql 127.0.0.1 1234567890 ready

依次是:PID、数据目录、启动时间戳、端口、Unix socket 目录、监听地址、共享内存标识、状态。最后那个ready很关键,它说明实例已经完成恢复、可以接受连接;如果是starting或者shutting down,那就对应两种经典的报错场景:应用连上来报the database system is starting up或者the database system is shutting down。

postmaster.pid最大的价值是判断残留。启动时报lock file already exists,说明文件还在。这时候不要急着删,先做两件事:

cat /var/lib/postgresql/14/main/postmaster.pid | head -1 ps -p $(head -1 /var/lib/postgresql/14/main/postmaster.pid)

如果ps能查到这个 PID 且确实是 postgres 进程,说明实例还在跑,你遇到的是「端口被占」或者「你在重复启动」,这时候该做的是查状态而不是删文件。如果ps查不到这个 PID,那就是上次非正常退出留下的残留文件,确认没有其他 postgres 进程后可以删掉再启动。我踩过的坑是:容器环境下 PID 会被复用,你查到的 PID 恰好被另一个无关进程占用了,看着像「还在跑」,其实数据库早死了。所以更稳的判断是看ps -ef里有没有对应数据目录的 postmaster 进程,而不是只看 PID 号。

2. 停止命令到底发了什么信号

很多人把pg_ctl stop当成一个黑盒,「反正它能让库停下来」。但停库有三种模式,模式不同,发出去的信号不同,行为差异很大,选错模式轻则业务报错一片,重则下次启动多花几十分钟做崩溃恢复。

2.1 smart、fast、immediate 三档停机,语义完全不同

PostgreSQL 的停机模式本质是 Unix 信号,理解了信号就理解了行为:

模式对应信号行为典型耗时
smartSIGTERM等所有客户端主动断开后才开始关闭,新连接仍然接受不可控,可能永远等不到
fastSIGINT立刻断开所有客户端、回滚未提交事务,然后做一次 checkpoint 再退出秒级到几十秒
immediateSIGQUIT不等待、不做 checkpoint,直接退出,下次启动走崩溃恢复毫秒级

smart这个模式我基本不用,原因很直白:它要等所有会话自己退出。生产库上只要有连接池挂着长连接、有一个忘了关的 psql 窗口、有一个跑了半天的批处理,它就一直等。你敲完命令回车,终端就那么挂着,你以为卡死了,其实它老老实实在等客户端。曾经有个同事在测试环境用 smart 停库,结果那个环境里挂着一个pg_sleep(3600)的会话,命令硬生生挂了四十分钟,最后还得另开窗口kill。

fast是日常停库的默认选择,pg_ctl stop不带-m参数时默认就是它。它会主动踢掉客户端、回滚事务,然后做一次 checkpoint 把脏页刷下去,保证下次启动不需要恢复。业务的感受是「连接突然断了」,所以要么挑低峰,要么让应用端有重连机制。

immediate是紧急刹车,对应pg_ctl stop -m immediate。注意它不是kill -9:它仍然是 postmaster 主动退出,共享内存会被释放,只是跳过了 checkpoint,把「一致性」这件事交给下次启动时的 WAL 重放。什么时候用它?实例已经进入不正常状态、fast 模式等了几分钟还不退出、磁盘快满了需要立刻停写。用它就要接受一个后果:下次启动会变慢,因为要从最近的 checkpoint 位置开始重放 WAL。

2.2 停机耗时能算出来,别凭感觉设超时

fast 模式的耗时大头是 checkpoint:把shared_buffers里的脏页刷到磁盘。这个时间是可以粗算的。假设shared_buffers设了 8GB,某个时刻脏页占比 25%,那就是 2GB 待刷数据。机械盘顺序写按 120MB/s 算,约 17 秒;SATA SSD 按 450MB/s 算,约 4.5 秒;NVMe 按 1.5GB/s 算,不到 2 秒。这还只是写盘时间,实际还要加上 fsync 的延迟、元数据操作,所以真实耗时通常是理论值的 1.5 到 3 倍。

这个估算不是纸上谈兵,它直接决定了两个参数该设多少:

# pg_ctl 等待启动/停止完成的超时,单位秒 pg_ctl -D /var/lib/postgresql/14/main -m fast -w -t 120 stop

-w表示等待操作真正完成,-t 120是超时 120 秒。不写-w的话命令会立刻返回,你以为停完了,其实后台还在刷盘,紧接着你去启动就撞上「already running」。我建议所有脚本里都显式带上-w -t。

systemd 这一侧对应的参数是TimeoutStopSec,默认值因发行版而异,常见 90 秒。如果你的库很大、shared_buffers给得很足,fast 停机可能超过这个时间,systemd 等不及会直接发 SIGKILL。SIGKILL 一上来,postmaster 没有任何清理机会,共享内存段残留、postmaster.pid残留,下次启动必须做完整的崩溃恢复,本来只要几十秒的事情变成几十分钟。所以大内存实例一定要检查这个值:

systemctl show postgresql-14 -p TimeoutStopSec

不够就改 unit 的 override:

# /etc/systemd/system/postgresql-14.service.d/override.conf [Service] TimeoutStopSec=300

改完systemctl daemon-reload生效。这一步我在好几个内存 64G 以上的实例上都做过,属于典型的「平时看不出问题,出事时救命」的配置。

3. 启动:从敲下回车到能接连接,中间发生了什么

pg_ctl start返回成功,不代表数据库就能用了。理解启动过程,才能解释那些「明明启动了却连不上」的怪现象。

3.1 启动流程拆开看

postmaster 启动后大致这么走:读postgresql.conf和postgresql.auto.conf→ 检查postmaster.pid→ 创建共享内存和信号量 → 启动 checkpointer、background writer、walwriter、autovacuum launcher 等后台进程 → 检查pg_control文件判断上次是否正常关闭 → 如果不正常就进入崩溃恢复,从最近的 checkpoint 开始重放 WAL → 打开监听端口 → 状态写成ready。

这条链路里每一步都可能卡住,对应的现象也不一样:

  • 卡在读配置:配置文件语法错误,日志里会有syntax error in file,进程直接退出。
  • 卡在 pid 检查:lock file already exists。
  • 卡在共享内存:could not create shared memory segment,通常是内核shmmax、shmall限制,或者huge_pages配置和系统不匹配。
  • 卡在崩溃恢复:日志里刷redo starts at ...,此时端口已经监听但拒绝连接,报the database system is starting up。

最后这种最容易被误判。有人看ss -lntp发现 5432 在监听,就断定「服务起来了」,然后应用连不上,开始怀疑网络、怀疑防火墙,查半天。其实看日志一行就明白了:

tail -f /var/log/postgresql/postgresql-14-main.log # 或者源码安装的 tail -f $PGDATA/log/postgresql-*.log

崩溃恢复的进度在日志里是可见的,redo done at ...出现才算恢复完。恢复耗时取决于两件事:上次 checkpoint 之后产生了多少 WAL,以及磁盘的随机读能力。粗算方法是看pg_wal目录里 checkpoint 之后的段文件数量,一个 16MB,乘以段数就是需要重放的数据量上限。如果业务写入是 500MB/min,checkpoint_timeout是 5 分钟,那最坏情况要重放 2.5GB 左右,机械盘上可能就是几分钟的事。

3.2 启动命令的正确写法

pg_ctl启动我一般这么写:

pg_ctl -D /var/lib/postgresql/14/main \ -l /var/log/postgresql/postgresql-14-main.log \ -w -t 300 \ start

-l指定日志文件很重要。不指定的话启动日志会打到当前终端,你 SSH 一断,日志就没了,出了事只能靠猜。有些环境里logging_collector = on,日志由数据库自己管理,这时候-l可以省,但启动早期(配置还没解析完)的报错仍然只会出现在 stderr,所以我还是习惯带上。

启动完成后别急着走,用pg_isready探一下:

pg_isready -h 127.0.0.1 -p 5432 -t 5 # /var/run/postgresql:5432 - accepting connections

这个命令返回 0 才代表真的能接受连接,比ps看进程靠谱得多。写脚本做健康检查时,pg_isready加psql -c 'select 1'双层校验是我常用的组合,前者快,后者能验证认证和权限链路。

还有一种情况必须提:PostgreSQL 不允许用 root 启动。你用 root 敲pg_ctl start,会直接看到pg_ctl: cannot be run as root。这是故意设计的,防止数据库进程拥有过高权限。正确做法是su - postgres切过去再操作,或者用sudo -u postgres pg_ctl ...。用 systemd 的话不存在这个问题,unit 文件里已经指定了User=postgres。

3.3 开机自启这件事,装完就该确认

新装完的实例,很多人只测了「手动能起来」,没测「重启机器能自己起来」。我接手环境时的标准动作是:

systemctl is-enabled postgresql-14 systemctl enable postgresql-14

Debian 系则是systemctl is-enabled postgresql。源码编译 +pg_ctl管理的情况没有 systemd 帮忙,要么写个 init 脚本,要么在crontab里加@reboot,要么干脆用pg_ctl的-w配合一个启动脚本。这一条看着不起眼,但机器意外重启后数据库没自己起来,业务停摆几个小时的事故我见过不止一次。

4. reload 还是 restart,改完配置这一步最容易搞错

这是我最想强调的一节。很多人改完postgresql.conf,条件反射就是systemctl restart。能生效,但代价是:所有连接断开、缓冲池清空、恢复期业务抖动。而实际上八九成的参数根本不需要重启,reload就够了。

4.1 三类参数,三种生效方式

参数类型生效方式典型参数
动态参数SIGHUP 即可,即 reloadwork_mem、maintenance_work_mem、log_min_duration_statement、checkpoint_completion_target、max_connections(新连接生效)
需重载共享内存必须 restartshared_buffers、wal_buffers、max_connections(已存在会话的上限)
全局结构参数必须 restartport、listen_addresses、data_directory、wal_level、max_wal_senders、huge_pages

reload 的本质是给 postmaster 发 SIGHUP,让它重新读配置文件,然后把变化广播给子进程。命令有三种写法,效果一样:

# 方式一 pg_ctl -D /var/lib/postgresql/14/main reload # 方式二 systemctl reload postgresql-14 # 方式三,在 psql 里 SELECT pg_reload_conf();

怎么知道哪些参数改了需要重启?PG 9.5 之后有个特别实用的视图:

SELECT name, setting, pending_restart FROM pg_settings WHERE pending_restart = true;

只要这个查询有结果,说明你已经改了某些参数但还没重启,新值还没真正生效。这个视图应该在每次改完配置后都查一遍,比翻文档可靠。我第一次见到它的时候还挺惊讶——原来数据库自己知道哪些改动在等重启,不用去背参数清单。

4.2 改配置的标准流程

我自己的操作顺序固定成这样,几乎不出错:

# 1. 备份配置 cp $PGDATA/postgresql.conf $PGDATA/postgresql.conf.bak.$(date +%Y%m%d) # 2. 改参数(用 ALTER SYSTEM 更规范,会写到 postgresql.auto.conf) psql -c "ALTER SYSTEM SET work_mem = '8MB';" # 3. 验证语法,先 reload psql -c "SELECT pg_reload_conf();" # 4. 看日志有没有报错 tail -20 /var/log/postgresql/postgresql-14-main.log # 5. 确认是否还有待重启参数 psql -c "SELECT name FROM pg_settings WHERE pending_restart;"

用ALTER SYSTEM而不是直接编辑postgresql.conf有一个好处:改动集中在postgresql.auto.conf里,和手工维护的主配置分开,出问题好回滚,也避免多人编辑同一文件互相覆盖。代价是ALTER SYSTEM不能改需要重启才能生效的参数——它其实能写进去,但会提示你重启才生效,容易忘。所以改这类参数我还是会去编辑主配置文件,改完立刻pending_restart查一遍。

有个细节值得说:max_connections比较特殊,它属于「改了之后新连接能看到新上限,但共享内存结构在启动时就定死了」。所以如果你把 100 调到 200 然后只 reload,新连接能连进来,但一旦超过旧上限的并发真正压上来,可能触发共享内存相关的异常。我的建议是这类参数一律走 restart,别图省事。

4.3 那些「reload 了但没生效」的坑

最典型的是改了pg_hba.conf之后 reload,然后发现新加的网段还是连不上。先确认三件事:改的文件是不是$PGDATA下那个(有些环境用hba_file参数指到了别处)、reload 有没有真的成功(看日志里有没有received SIGHUP)、以及客户端走的是 TCP 还是 Unix socket——pg_hba.conf里local和host是两条完全不同的规则。

另一个坑是配置文件里的include或include_dir。主配置里可能引了conf.d/*.conf,你改的是一份,实际生效的是另一份。查当前实际生效值,别靠眼睛看文件:

SHOW work_mem; SELECT * FROM pg_file_settings WHERE error IS NOT NULL;

pg_file_settings这个视图会列出所有被加载的配置文件行,语法错误也会标出来,比逐行检查高效得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进