PostgreSQL 小版本升级实战:14.23 升级到 14.24(源码编译方式)

同一大版本内的次版本升级,不需要 pg_upgrade,编译新版本覆盖二进制即可。本文给出完整流程、每步的判定标准,以及七个实际踩过的坑。适用于源码编译部署、离线内网、不允许停机太久的场景。

一、先弄清楚什么时候需要 pg_upgrade

PostgreSQL 的版本号是三段式:主版本.次版本,例如 14.23。第一个数字是主版本(major),后面的是次版本(minor)。

两种情况,处理方式完全不同:

场景 示例 数据目录格式 处理方式
主版本升级 13.x → 14.x 不兼容,需要转换 pg_upgrade 或 pg_dumpall 导出再导入
次版本升级 14.23 → 14.24 兼容,无需转换 编译新版本,覆盖二进制,直接启动

次版本升级不改数据目录格式,PostgreSQL 官方把次版本限定为安全修复与缺陷修复,不引入格式变更。所以升级的本质就是把 /usr/local/pgsql/bin 下的程序换成新版本,数据文件一个字节不动。

这种方式的三个特点:

  • 快:不涉及数据转换,停服时间取决于备份与编译耗时,通常 20 到 40 分钟
  • 安全:数据文件全程不参与,只要参数不出错,数据不会损坏
  • 风险集中:所有风险集中在两个环节,参数配错与属主搞错

本文按六步走,每一步给出判定标准,出现不符合的情况就停下来处理。

二、升级前必须做的三件保险

2.1 全量备份

数据库运行中执行,不要先停库。停库之后备份当然也能做出文件,但那样一旦备份出了问题、库又停了,就没有退路了。

复制代码
su postgres
pg_dumpall -U postgres > /data/pgdata/full_backup_$(date +%Y%m%d).sql

三个细节:

pg_dumpall 而不是 pg_dump 。前者备份整台实例,包含全部数据库、数据库角色、建库语句;后者只备份单个库,且不含 CREATE DATABASE。回滚时需要整实例恢复,必须用 pg_dumpall。

-U postgres 不能省 。本地 socket 连接默认用当前系统用户名当角色名,以 root 身份执行会报 role "root" does not exist。

$(date +%Y%m%d) 自动生成日期,便于区分多次备份,也避免手工输入出错。

命令成功时屏幕上什么都不会显示 ,直接回到提示符。这不是卡住,是 pg_dumpall 的默认行为。数据量大时可能静默几分钟到十几分钟,期间不要按 Ctrl+C,中断会得到一个不完整的备份文件。

2.2 备份有效性校验

命令没报错,不代表备份可用。必须核对三项。

复制代码
ls -lh /data/pgdata/full_backup_*.sql

预期:

复制代码
-rw-r--r--. 1 postgres postgres 4.8K 10月 21 14:51 full_backup_20261021.sql

第一项,文件大小非零。 这是最容易出事的地方。shell 的输出重定向是「先创建文件、再执行命令」,命令失败时文件已经留下了,形成一个 0 字节的空文件,名字和正常备份完全一样。之后如果拿这个文件去恢复,等于没有备份。

第二项,属主是 postgres。 如果显示 root root,后续用 postgres 身份覆盖会失败,因为目录权限只作用于新建动作,不改变已有文件的权限。修正:

复制代码
chown postgres:postgres /data/pgdata/full_backup_20261021.sql

第三项,内容含建库语句。

复制代码
grep -c "CREATE DATABASE" /data/pgdata/full_backup_*.sql

返回大于 0 的数字即正常,返回 0 说明备份不完整需要重做。

再抽查文件头:

复制代码
head -20 /data/pgdata/full_backup_*.sql

应该看到 PostgreSQL database dump 字样和版本标记(本场景是 Dumped from database version 14.23)。

口诀:备份三查,查大小、查属主、查内容。

2.3 回滚条件

两种,至少满足一种:

虚拟机快照 。在虚拟化平台对服务器打快照,名称带日期便于识别,例如 before_pg1424_20261021。恢复时间通常十分钟以内,是最可靠的退路。

备份文件 + 旧版本源码 。没有快照条件时,备份文件与旧版本源码目录是唯一的退路。旧版本源码目录(如 /opt/postgresql-14.23)在验证通过前不能删。

两者都没有,不要开始升级。

三、停服

3.1 顺序:先应用,后数据库

复制代码
systemctl stop appsvc
systemctl stop postgresql.service
systemctl status postgresql.service

appsvc 是应用服务名,按实际环境的服务名替换。顺序反了会怎样:应用还在运行并不断尝试重连数据库,日志里刷满连接失败,数据库也可能因为活跃连接而停不干净。

系统上有 service 和 systemctl 两套命令,service appsvc stop 与 systemctl stop appsvc 效果相同,后者是新写法,优先用 systemctl。

3.2 确认真的停了

复制代码
ps -ef | grep postgres | grep -v grep

必须无输出。有输出说明进程还在,等十秒重试停止命令,多次无效时记下 PID 单独终止:

复制代码
kill <PID>

数据库进程没退干净就替换二进制,可能导致数据文件状态不一致。这一步不能省。

3.3 关于 active (exited)

停完之后 systemctl status 会显示:

复制代码
Active: inactive (dead)

启动后则是:

复制代码
Active: active (exited)

active (exited) 这个状态容易被误读成「服务已经退出了」。实际情况是:这类服务由 SysV init 脚本经 systemd 包装生成,查看 Loaded 一行能看到 (/etc/rc.d/init.d/postgresql; generated) 这样的路径与 generated 标记。启动脚本执行完 fork 出后台进程后就退出了,systemd 记录脚本的退出状态为 active,但不再跟踪数据库进程的 PID。

所以:

  • active (exited) 不代表数据库能接受连接
  • 数据库是否可用,只能靠 psql 连一次确认

这个状态还有一个副作用:不能用 systemctl restart 的返回结果判断数据库是否已经就绪,重启后要等几秒再连。

四、编译新版本

4.1 解压

复制代码
cd /opt
tar -xzf /root/postgresql-14.24.tar.gz
ls -d /opt/postgresql-14.24

解压前先校验安装包:

复制代码
md5sum postgresql-14.24.tar.gz

与官方 md5 文件比对,一致才继续。

4.2 configure 参数必须与原环境完全一致

这是整个升级过程中最关键的一步。

复制代码
cd /opt/postgresql-14.24
./configure --prefix=/usr/local/pgsql --with-pgport=5433 --datadir=/data/pgdata

三个参数一个都不能改:

参数 写错会怎样
--prefix 新程序装到别处,系统仍调用旧程序,select version() 还是 14.23,看着像升级失败
--with-pgport 默认端口变化。已有 postgresql.conf 里显式指定了端口时不受影响,但任何依赖默认值的脚本会连错端口
--datadir 时区文件、系统视图定义装到别处,启动时找不到时区文件,直接报错退出

参数不是靠记忆,要靠反查。 两种方法:

复制代码
# 方法一:从已有安装里读
/usr/local/pgsql/bin/pg_config --configure

# 方法二:如果旧版本源码树还在
cd /opt/postgresql-14.23
grep -E "^(prefix|datadir|DEF_PGPORT) " src/Makefile.global

方法一会打印出当初编译时的完整参数列表,最可靠。

4.3 --datadir 不是数据目录

这一点需要单独说明,因为踩过的人不少。

configure --help 里对 --datadir 的描述是:

复制代码
--datadir=DIR           read-only architecture-independent data [DATAROOTDIR]

只读数据 ,指 share 目录下的内容:时区文件、postgres.bki、系统视图 SQL、全文检索词典。数据库数据目录(PGDATA,存放 base、global、pg_wal 的那个)由 initdb -D 决定,与编译参数无关。

但实际部署中经常出现 --datadir 与 PGDATA 指向同一路径的情况。这时 make install 会以 root 身份往数据目录里写时区文件,文件属主变成 root,而数据库进程以 postgres 运行读不了,启动报错。这就是下一节要处理的问题。

反查当前环境实际用的什么路径:

复制代码
find / -name postgresql.conf -type f 2>/dev/null | grep -v sample

返回路径的父目录就是 PGDATA。如果它和 pg_config --configure 里的 --datadir 相同,说明两者重合,安装后必须处理属主。

4.4 核对参数是否真的写进去了

configure 接受参数不等于参数生效。唯一的证据是生成的 Makefile.global:

复制代码
grep -E "^(prefix|datadir|DEF_PGPORT) " src/Makefile.global

预期:

复制代码
prefix = /usr/local/pgsql
datadir = /data/pgdata
DEF_PGPORT = 5433

三行都要在,数值与预期一致。缺行或数值不对,说明参数没传进去,执行 make distclean 清除后重新配置。直接重跑 configure 可能沿用之前的缓存结果。

4.5 编译

复制代码
make -j$(nproc); echo "退出码=$?"

只看退出码,不看输出。 PostgreSQL 14 的 Makefile 不会打印总结性的成功提示,输出几万行里找不到「successfully」这种字样是正常的,别在日志里翻。

  • 退出码=0:成功
  • 其他:失败

失败时定位:

复制代码
make -j$(nproc) 2>&1 | grep -n "error:" | head -20

4.6 确认编译产物

复制代码
src/backend/postgres --version

postgres (PostgreSQL) 14.24

注意这是源码目录里的程序,还没装到系统。这里对只说明编译成功。

五、安装后的两件必做

5.1 安装

复制代码
make install; echo "退出码=$?"

退出码 0 即成功。

5.2 修正时区文件属主

这是所有步骤里最容易漏、后果最直接的一步。

复制代码
find / -name "timezone" -type d 2>/dev/null

返回结果可能有两类路径:

  • $PGDATA/timezone(当 --datadir 与 PGDATA 重合时)
  • $prefix/share/timezone(默认情况)

以 find 返回的实际路径为准,然后整目录修正:

复制代码
chown -R postgres:postgres /data/pgdata/timezone
chown -R postgres:postgres /data/pgdata/timezonesets

-R 不能省。时区目录下按洲和城市分层存放,例如 timezone/Asia/Shanghai,只改 timezone 目录本身的属主,里面的文件还是 root,照样读不了。

核对:

复制代码
ls -ld /data/pgdata/timezone
ls -ld /data/pgdata/timezonesets

预期两行都以 postgres postgres 开头。

为什么这一步必须做 :make install 由 root 执行,写进去的文件属主是 root。数据库进程以 postgres 身份运行,对这些文件没有读权限。启动时会报:

复制代码
FATAL: could not open file ".../timezone/Asia/Shanghai": Permission denied

有些文档建议等报错之后再处理,那时服务已经起不来,排障时间更长。主动在启动前做完,省事。

5.3 确认系统目录的程序版本

复制代码
/usr/local/pgsql/bin/postgres --version

postgres (PostgreSQL) 14.24

这一步和 4.6 的区别:4.6 查源码目录,这里查系统安装目录。两处都是 14.24,才算安装真正覆盖成功。如果这里还是 14.23,检查 --prefix 是否写错,然后重跑 make install。

5.4 更新共享库

复制代码
echo /usr/local/pgsql/lib > /etc/ld.so.conf.d/pgsql.conf
ldconfig

两条命令都无输出,安静返回即成功。作用是让系统动态链接器找到新版本的 libpq.so。省略的话,链接 PostgreSQL 库的程序在运行时可能加载到旧版本库或报找不到。

如果这个配置文件已存在且内容相同,第一条可省略,直接 ldconfig。

六、启动与验证

6.1 启动

复制代码
systemctl start postgresql.service
systemctl status postgresql.service

看到 Starting PostgreSQL: ok 与 Active: active (exited) 即为脚本执行成功。

无 systemd 的环境用 pg_ctl:

复制代码
su postgres
pg_ctl -D /data/pgdata -l /data/pgdata/logfile start

预期 server started。

两种方式不要混用 。用 systemctl 启动的实例,systemd 记录了启动脚本的 PID;用 pg_ctl 启停时 systemd 不知情,systemctl status 显示的状态会与实际不符,排查时容易误判。

6.2 启动失败看日志

复制代码
tail -50 /data/pgdata/logfile

日志位置取决于启动方式:用 pg_ctl 启动时是 -l 参数指定的文件;用 systemctl 启动时一般在数据目录下,文件名可能是 logfile 也可能是 serverlog。不确定时按时间排序看:

复制代码
ls -lt /data/pgdata/ | head -10

日志里以 FATAL 开头的行就是失败原因。

6.3 验证版本:必须查服务端

复制代码
su postgres
psql -U postgres -c "select version();"

预期:

复制代码
PostgreSQL 14.24 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514, 64-bit

这是判定升级成功的唯一依据。

只跑 psql --version 是不够的:

复制代码
psql --version
# psql (PostgreSQL) 14.24

这条查的是客户端程序 版本。客户端与服务端是两个进程,可能版本不一致。升级时如果只换了部分二进制,或者 PATH 指向了不同目录,完全可能出现客户端 14.24、服务端还是 14.23 的情况,而 psql --version 显示正常,误导判断。

两条都跑,都显示 14.24 才稳:

复制代码
psql --version
psql -U postgres -c "select version();"

6.4 验证数据

版本对了,还要确认数据在。三条命令,都在 postgres 身份下执行。

复制代码
# 库清单
psql -U postgres -c "\l"

# 业务库的表清单
psql -U postgres -d appdb -c "\dt"

# 关键表行数
psql -U postgres -d appdb -c "select count(*) from devices;"

appdb 与 devices 按实际替换。预期库数量、表数量、行数与升级前一致。

升级前建议先采集一份基线,升级后逐项对比:

复制代码
psql -U postgres -c "select version();"
psql -U postgres -c "\l"
psql -U postgres -d appdb -c "\dt"
psql -U postgres -d appdb -c "select count(*) from devices;"
grep -E "^listen_addresses|^port|^max_connections" /data/pgdata/postgresql.conf

6.5 启动应用

数据核对通过后再起应用,顺序不能反。数据库没起好就起应用,应用会连续报连接失败,日志里一堆噪音,掩盖真正的问题。

复制代码
systemctl start appsvc
systemctl status appsvc

预期 active (running)。

然后登录应用界面,确认设备列表、策略配置等页面能正常打开、数据能正常显示。应用报数据库连接错误时,按顺序查四项:

  1. 应用服务是否已重启并加载了新配置
  2. 应用配置里的数据库地址与端口是否与实际一致
  3. pg_hba.conf 是否允许应用主机连接
  4. postgresql.conf 的 listen_addresses 是否与升级前一致

七、七个实际踩过的坑

7.1 备份文件 0 字节

现象 :ls -lh 显示大小为 0。

原因:命令执行失败,但重定向已经先创建了文件。

处理:删掉文件,查明失败原因后重做,重新核对大小。

这个坑的危险在于文件名字与正常备份完全一样,ls 不看大小的话根本发现不了。真到需要回滚那天才发现备份是空的,就没有退路了。

7.2 role "root" does not exist

现象:

复制代码
pg_dumpall: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed:
FATAL:  role "root" does not exist

原因 :命令漏了 -U postgres,以 root 身份连接时 PostgreSQL 拿系统用户名当角色名。

处理 :补上 -U postgres。所有数据库命令统一带这个参数,不管当前是什么身份。

7.3 权限不够(备份写不进目录)

现象:

复制代码
-bash: /data/pgdata/full_backup.sql: 权限不够

原因:目录或目标文件属主是 root,postgres 没有写权限。

处理:

复制代码
chown postgres:postgres /data/pgdata
rm -f /data/pgdata/full_backup.sql

rm 那步不能省。已有的 root 属主文件,即使目录交给了 postgres,postgres 也无法覆盖它,因为文件本身的权限没变。必须删掉让 postgres 重新创建。

7.4 时区文件 Permission denied

现象 :启动失败,日志报 could not open file ".../timezone/Asia/Shanghai"。

原因 :make install 以 root 写入,时区文件属主是 root。

处理:

复制代码
find / -name "timezone" -type d 2>/dev/null
chown -R postgres:postgres <找到的路径>
chown -R postgres:postgres <同级的 timezonesets 目录>

7.5 systemctl 报 Unit not found

现象:

复制代码
Failed to start postgresql.service: Unit postgresql.service not found.

原因 :源码编译安装不会创建 systemd 单元。只有通过 rpm 或安装脚本部署的环境才有 postgresql.service。

处理 :改用 pg_ctl,或者自己写单元文件。判断环境属于哪一类:

复制代码
ls /etc/rc.d/init.d/postgresql 2>/dev/null && echo "有 SysV 脚本"
ls /usr/lib/systemd/system/postgresql* 2>/dev/null && echo "有 systemd 单元"

两者都没有,就是纯源码安装,只能用 pg_ctl。

7.6 端口被占用

现象 :日志报 could not bind 或 address already in use。

原因:旧进程没退干净,或别的程序占了同一端口。

处理:

复制代码
ps -ef | grep postgres | grep -v grep
ss -lntp | grep 5433

确认是旧数据库进程后再 kill,别误杀其他服务。

7.7 数据目录版本不匹配

现象:

复制代码
FATAL: database files are incompatible with server

原因 :启动的程序与数据目录的版本不一致。常见两种情况:安装没成功(make install 覆盖失败),或者启动时调用的是错误路径下的程序(PATH 里有旧版本目录)。

处理:

复制代码
/usr/local/pgsql/bin/postgres --version    # 安装目录的程序版本
which pg_ctl                                # 实际调用的是哪个 pg_ctl
ls -l /etc/rc.d/init.d/postgresql           # 服务脚本里写的是哪个路径

三处都要指向新版本。which pg_ctl 指向旧目录时,说明 PATH 里有旧版本残留,用绝对路径启动。

八、回滚

8.1 方式一:快照回滚

虚拟化平台恢复到升级前的快照。整机回到 14.23 与升级前数据,十分钟内完成,结果确定。

注意:恢复会丢失升级后服务器上的所有变更,包括升级操作之外的其他改动。恢复前确认没有需要保留的新内容。

8.2 方式二:重编旧版本 + 备份恢复

没有快照条件时使用。

第一步,停服:

复制代码
systemctl stop appsvc
systemctl stop postgresql.service

第二步,重新编译安装旧版本。参数与升级时完全一致:

复制代码
cd /opt/postgresql-14.23
./configure --prefix=/usr/local/pgsql --with-pgport=5433 --datadir=/data/pgdata
make -j$(nproc)
make install
ldconfig
chown -R postgres:postgres /data/pgdata/timezone

第三步,恢复数据:

复制代码
su postgres
psql -U postgres -d postgres -f /data/pgdata/full_backup_20261021.sql

恢复过程会输出大量 SQL 执行信息,中间出现若干 already exists 提示属正常,因为库和表已经存在。最后没有 FATAL 或 ERROR 即为完成。

第四步,启动并验证:

复制代码
systemctl start postgresql.service
su postgres
psql -U postgres -c "select version();"

预期回到 14.23。

8.3 回滚的两个注意点

备份由哪个版本生成,就用哪个版本的 psql 恢复。 14.23 生成的备份文件头里有对应的版本标记和指令(例如较新版本会输出 \restrict),用不匹配的客户端执行可能出现语法错误。

回滚后同样要跑完整验证。 版本、数据、表结构、应用连接四项都正常,才算回滚完成。看到版本号变回 14.23 只是第一步。

九、判断清单

升级过程中按这张表逐项确认,任一项不符就停下来。

阶段 判定项 通过标准
备份 文件大小 不为 0
备份 文件属主 postgres postgres
备份 建库语句条数 大于 0
停服 服务状态 inactive (dead)
停服 残留进程 ps 查询无输出
解压 安装包 md5 与官方值一致
配置 Makefile.global 三项 prefix / datadir / DEF_PGPORT 与预期一致
编译 退出码 0
编译 源码目录程序版本 14.24
安装 退出码 0
安装 系统目录程序版本 14.24
安装 时区目录属主 postgres postgres
安装 ldconfig 无输出,正常返回
启动 服务状态 active (exited),日志含 Starting PostgreSQL: ok
验证 服务端版本 select version() 返回 14.24
验证 库与表数量 与升级前一致
验证 关键表行数 与升级前一致
验证 应用功能 页面正常,数据可显示

十、四句总结

升级这件事,命令不多,风险都集中在几个"不看就发现不了"的地方:

  • 备份只看文件在不在,不看大小
  • 参数凭记忆写,不反查原环境
  • 编译在几万行输出里找成功提示,不看退出码
  • 版本只查 psql --version,不查服务端

反过来记四句:

备份看大小,参数靠反查,编译看退出码,升级看服务端。

配套的安装过程可以看另一篇《PostgreSQL 14 源码编译安装实战》,两篇连起来刚好是一条完整的链路:从零装一个实例,再把它升到新版本。

相关推荐
LMT流氓兔1 小时前
第五单元——第11课:自动测试“拒绝后绝不能写入”
数据库·人工智能·python·langchain
handonch1 小时前
金仓 KingbaseES 时区问题根治指南:从「总是差 8 小时」到全链路统一
linux·docker·postgresql·信创
小朱爱编程1231 小时前
AI点了两次发布,我多了两篇事故
数据库·人工智能·大模型·软件工程
时间售卖铺1 小时前
MySQL DML 与 DQL 语句详解
运维·数据库·mysql·云原生
codeejun2 小时前
每日一Go·MySQL-9、索引存储结构|页分裂、页合并、索引高度与磁盘
数据库·mysql
Pniubi2 小时前
事务(Spring&MySQL)
数据库·sql·spring
DBA_G3 小时前
GBase 8a MPP集群个人电脑安装环境浅析
数据库
小林ixn3 小时前
别再 MySQL + Milvus 双写了:PostgreSQL + pgvector 一张表搞定 AI 长期记忆
数据库·postgresql·agent
深盾科技_Virbox3 小时前
换机或重装后软件许可怎么恢复:先核对哪四项
java·服务器·数据库