面向需要在生产服务器上自行编译部署 PostgreSQL 的场景,包含离线环境依赖处理、编译参数含义、初始化细节和十个高频报错的处置方式。全部命令在 RHEL 8.10 x86_64 上实测通过。
一、为什么选择源码编译
PostgreSQL 在 Linux 上常见三种安装方式,先看清楚差异再决定。
| 方式 | 来源 | 版本可控 | 安装路径 | 适用场景 |
|---|---|---|---|---|
| 官方仓库 rpm | yum install postgresql-server | 跟随仓库版本 | 固定,分散在多处 | 有外网、不挑版本 |
| 第三方仓库(PGDG) | yum install postgresql14-server | 可选大版本 | 固定 | 有外网、需要指定大版本 |
| 源码编译 | 下载 tar.gz 自行编译 | 精确到小版本 | 完全自定义 | 离线环境、需要指定次版本、需要追加编译参数 |
源码编译的核心价值在于版本与路径完全自主:项目要求部署 14.23 就必须是 14.23,不能是仓库里的 14.22 或 14.24;程序装在哪个目录、端口默认值是多少,也都能在编译时固定下来。代价是需要自己处理依赖、自己准备服务脚本、升级时要重新编译。
如果环境有外网且不挑次版本,直接用仓库安装更省事。本文的场景是企业内网离线环境,必须从源码构建。
二、环境准备
2.1 系统检查
cat /etc/redhat-release
uname -m
nproc
free -h
df -h /data /usr/local
预期输出示例:
Red Hat Enterprise Linux release 8.10 (Ootpa)
x86_64
16
total used free shared buff/cache available
Mem: 31Gi 2.1Gi 27Gi 0.0Gi 1.6Gi 28Gi
要点:
- 架构必须是 x86_64,源码包按平台区分,arm 需要另外下载对应版本
nproc返回的核数后面编译时要用,16 核编译 PostgreSQL 约 3 到 5 分钟,单核要 20 分钟以上- 磁盘空间:编译过程产生大量中间文件,数据目录与安装目录所在分区各留 2G 以上
2.2 离线环境处理依赖
离线环境最大的障碍是装不了编译依赖。解决办法是用系统安装 ISO 做本地 yum 源,这是内网部署的标准做法。
先确认 ISO 是否挂在光驱上:
lsblk
blkid /dev/sr0
blkid 能输出文件系统类型和卷标,说明光驱有介质。如果提示找不到媒体,需要在虚拟化平台把 ISO 挂载并勾选「已连接」,光驱里的介质识别不到时 lsblk 的 SIZE 列可能显示为 0,不可靠,以 blkid 为准。
挂载并配置本地源:
mkdir -p /mnt/iso
mount /dev/sr0 /mnt/iso
ls /mnt/iso
能列出 BaseOS 和 AppStream 两个目录即挂载成功。挂载时出现「写保护,将以只读方式挂载」的提示属正常,光盘本身就是只读的。
printf '[Local-BaseOS]\nname=Local-BaseOS\nbaseurl=file:///mnt/iso/BaseOS\nenabled=1\ngpgcheck=0\n[Local-AppStream]\nname=Local-AppStream\nbaseurl=file:///mnt/iso/AppStream\nenabled=1\ngpgcheck=0\n' > /etc/yum.repos.d/local-iso.repo
RHEL 8 的安装介质把软件包拆在 BaseOS 和 AppStream 两个仓库里,两个都要配。写成 .repo 文件而不是直接改 /etc/yum.repos.d/redhat.repo,是为了不覆盖系统原有配置。
三、安装编译工具链
3.1 四个必需依赖
dnf install -y gcc make readline-devel zlib-devel
| 包 | 作用 | 缺了会怎样 |
|---|---|---|
| gcc | C 编译器 | configure 报 no acceptable C compiler found in $PATH,直接中断 |
| make | 构建工具 | configure 通过但编译无法开始 |
| readline-devel | 命令行编辑库 | 能编译,但 psql 里上下键翻不了历史命令、左右键不能编辑,且没有自动补全 |
| zlib-devel | 压缩库 | 能编译,但 pg_dump 无法输出压缩格式,备份体积翻几倍 |
后两个是「缺了不报错、但装出来不好用」的类型,configure 遇到可选依赖缺失会自动降级,不会中断。这一点下面还会专门讲。
RHEL 未注册订阅时,执行 dnf 会提示订阅相关警告,本地源照样可用,可以忽略。如果 dnf 仍然尝试联网,强制指定仓库:
dnf --disablerepo=* --enablerepo=Local-BaseOS --enablerepo=Local-AppStream install -y gcc make readline-devel zlib-devel
3.2 验证
gcc --version | head -1
make --version | head -1
rpm -q readline-devel zlib-devel
预期:
gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21)
GNU Make 4.2.1
readline-devel-8.0-4.el8.x86_64
zlib-devel-1.2.11-25.el8.x86_64
四、获取并校验源码包
PostgreSQL 源码在官方 ftp 站点发布:
https://ftp.postgresql.org/pub/source/v14.24/postgresql-14.24.tar.gz
把版本号换成需要的版本即可。该目录下同时有 .md5 文件和 .sha256 文件,一并下载用于校验。
上传到服务器后先算校验值:
cd /root
md5sum postgresql-14.24.tar.gz
与官方 md5 文件逐位比对,完全一致才继续。这一步不能省:安装包在跨网传输、U 盘拷贝过程中损坏并不罕见,损坏的包可能编译中途报出难以理解的错误,也可能编译成功但运行时行为异常,排查成本远高于十秒的校验。
cat postgresql-14.24.tar.gz.md5
五、configure 参数详解
这一步是整个安装过程中最容易出错的环节,参数写错会导致程序装到错误位置,或者运行时找不到配置文件。
5.1 三个核心参数
tar -xzf postgresql-14.24.tar.gz -C /opt
cd /opt/postgresql-14.24
./configure --prefix=/usr/local/pgsql --with-pgport=5432 --datadir=/usr/local/pgsql/share
| 参数 | 含义 | 影响 |
|---|---|---|
--prefix |
安装根目录 | 程序装到 $prefix/bin,库到 $prefix/lib,头文件到 $prefix/include |
--with-pgport |
默认端口 | 未在 postgresql.conf 中显式指定端口时使用此值 |
--datadir |
只读数据目录 | 时区文件、系统视图定义、全文检索词典、postgres.bki 等不随数据变化的文件 |
5.2 关于 datadir 的一个常见误解
很多人以为 --datadir 指定的是数据库数据目录(存放 base、global、pg_wal 的那个目录),其实不是。
翻 configure 的 --help 可以确认:
./configure --help | grep -A1 "datadir"
--datadir=DIR read-only architecture-independent data [DATAROOTDIR]
注意 read-only 这个词。它指的是只读数据 ,也就是 share 目录下的内容,不含任何业务数据。
数据库数据目录(PGDATA)由初始化命令 initdb -D 指定,与编译参数无关。这一点在实际部署中会造成一个很隐蔽的问题:如果把 --datadir 指向了数据目录的路径,那么 make install 会以 root 身份把时区文件写进数据目录,而这些文件属主变成 root,数据库进程以 postgres 身份运行读不了,启动时直接报错退出。
这个坑在生产环境上出过一次,排查花了不少时间。后面升级那篇文章会详细讲怎么处理。
默认不指定 --datadir 时,它等于 $prefix/share,多数场景用默认值即可。
5.3 如何反查已有环境的编译参数
接手别人的环境、或者要对齐现有配置时,不能靠猜。两种查法:
# 方法一:看已有安装的 pg_config
/usr/local/pgsql/bin/pg_config --configure
# 方法二:如果是编译源码树还在,看生成的 Makefile
grep -E "^(prefix|datadir|DEF_PGPORT) " src/Makefile.global
第一条会直接打印出当初 configure 时的完整参数列表,最可靠。如果旧环境已经没有源码树,这是唯一的途径。
5.4 判断 configure 是否成功
configure 的输出有几百行,不需要逐行看。只看结尾:
config.status: creating GNUmakefile
config.status: creating src/Makefile.global
config.status: creating src/include/pg_config.h
config.status: linking src/makefiles/Makefile.linux to src/Makefile.port
出现这几行且正常回到提示符,就是成功了。全文出现 configure: error: 才是失败。
输出里的 no 大多不用管:
checking for bison... no
checking for flex... no
checking for g++... no
checking for ICU... no
checking for LDAP... no
checking for PAM... no
bison、flex 在 bison 缺失时 configure 会打一行 WARNING,原文就写了官方发布包已预生成相关文件,不需要这两个工具。g++ 只影响 C++ 接口,不影响 C 核心。ICU、LDAP、PAM 是可选功能,没装就没有对应能力,不影响基本运行。
5.5 两个层次的判断
这里需要区分两个概念,很多人会混淆:
- 退出码回答「能不能往下走」。致命项(缺 gcc、参数写错、库冲突)会让 configure 中断并返回非 0。
- pg_config.h 里的宏回答「装出来好不好用」。缺 readline、缺 zlib 时 configure 照样返回 0,只是对应功能没编进去。
所以配置完成后,除了看退出码,还应检查关键宏:
grep -E "HAVE_LIBREADLINE|HAVE_LIBZ|USE_OPENSSL" src/include/pg_config.h
预期:
#define HAVE_LIBREADLINE 1
#define HAVE_LIBZ 1
/* #undef USE_OPENSSL */
前两个是 1 说明 readline 和 zlib 已编入;USE_OPENSSL 被注释掉说明没编 SSL 支持,如果需要加密连接,要先装 openssl-devel 再带 --with-openssl 重新配置。
记法:退出码管能不能走,宏文件管好不好用。
六、编译与安装
6.1 编译
make -j$(nproc); echo "退出码=$?"
-j$(nproc) 按 CPU 核数并发编译。16 核约 3 到 5 分钟,单核 20 分钟以上,差距明显。
输出量很大,几万行滚动是正常的。不要试图在输出里找成功提示 ,PostgreSQL 14 的 Makefile 并不会打印类似 All of PostgreSQL successfully made 这样的总结语(这句话在部分文档和旧版本教程里出现,实际 14.x 没有)。唯一可靠的判断是退出码:
退出码=0表示成功- 其他数字表示失败
编译失败时定位报错:
make -j$(nproc) 2>&1 | grep -n "error:" | head -20
6.2 确认编译产物
src/backend/postgres --version
postgres (PostgreSQL) 14.24
注意这条查的是源码目录里刚编出来的程序,还没有安装到系统。显示正确版本只说明编译成功,不代表系统已经升级或装好了。
6.3 安装
make install; echo "退出码=$?"
退出码为 0 即成功。安装过程把程序写入 --prefix 指定目录,把只读数据写入 --datadir 指定目录,同名旧文件被覆盖。
安装完成后确认系统目录里的程序版本:
/usr/local/pgsql/bin/postgres --version
6.4 登记共享库
echo /usr/local/pgsql/lib > /etc/ld.so.conf.d/pgsql.conf
ldconfig
作用是让系统动态链接器能找到 libpq.so 等库文件。省略这一步,自行编译并链接 PostgreSQL 库的程序在运行时会报找不到 libpq.so。两条命令都无输出,安静返回即为成功。
验证:
ldconfig -p | grep libpq
能列出 libpq.so.5 及其路径,说明登记生效。
七、初始化数据库实例
7.1 创建运行账号
PostgreSQL 拒绝以 root 身份运行,必须有一个普通账号承载数据库进程:
groupadd postgres
useradd -g postgres postgres
id postgres
预期:
uid=1001(postgres) gid=1001(postgres) 组=1001(postgres)
7.2 创建数据目录并赋权
mkdir -p /data/pgdata
chown postgres:postgres /data/pgdata
chmod 700 /data/pgdata
ls -ld /data/pgdata
预期:
drwx------. 2 postgres postgres 6 10月 21 10:12 /data/pgdata
权限必须是 700。PostgreSQL 初始化时会检查数据目录权限,权限过宽会直接拒绝执行,这是安全设计:数据目录里存放的是全部业务数据,不能允许其他用户读取。
如果父目录 /data 是 root 创建的,postgres 无法在下面新建子目录。此时由 root 先建好 pgdata,再把属主交给 postgres,不要试图给 /data 放宽权限。
7.3 初始化
su postgres -c "/usr/local/pgsql/bin/initdb -D /data/pgdata"
su postgres 后面不带横杠,这一点有讲究:
su postgres:切换身份但保留当前环境变量,PATH 里含$prefix/bin,可直接调initdbsu - postgres:加载 postgres 的登录环境,PATH 重置为默认值,不含$prefix/bin,需要写全路径
两种写法都能用,但混着用容易出错。本文统一用带横杠的 su - postgres -c "..." 或 su postgres -c "..." 加全路径,避免依赖环境变量。
成功输出结尾:
Success. You can now start the database server using:
/usr/local/pgsql/bin/pg_ctl -D /data/pgdata -l logfile start
初始化过程中的一条 WARNING 值得注意:
could not find suitable text search configuration for locale "zh_CN.UTF-8"
这是 WARNING 不是错误 ,初始化会继续并成功。原因是中文 locale 下没有匹配的全文检索词典配置,PostgreSQL 自动降级为 simple 配置。影响仅限于中文全文检索(to_tsvector),普通查询、索引、事务都不受影响。如果不需要全文检索功能,可以忽略。
要彻底避免,可以在 initdb 时指定 locale:
su postgres -c "/usr/local/pgsql/bin/initdb -D /data/pgdata -E UTF8 --locale=C"
C locale 下全文检索配置为 simple,行为一致但没有告警。注意 locale 一旦确定,后续修改需要重新初始化或重建数据库,初始化前想清楚。
7.4 配置环境变量
不要在 /etc/profile 里直接追加,用独立文件:
echo 'export PATH=/usr/local/pgsql/bin:$PATH' > /etc/profile.d/pgsql.sh
chmod 644 /etc/profile.d/pgsql.sh
source /etc/profile
/etc/profile 末尾有循环自动加载 /etc/profile.d/*.sh,等效但更干净:系统文件保持原样,要改要删都只动自己这一文件。
这里有一个高频错误:把 PATH 写成源码目录。
export PATH=/opt/pgsql/bin:$PATH # 错误:/opt/pgsql 是解压目录,下面没有 bin
export PATH=/usr/local/pgsql/bin:$PATH # 正确:$prefix/bin
源码解压目录(如 /opt/postgresql-14.24)里确实有一个 src/bin,但没有 bin。写成解压目录的 PATH 会出现「psql 找不到命令,但文件确实装好了」的怪现象。
另一个错误是漏了 :$PATH:
export PATH=/usr/local/pgsql/bin # 错误:整个 PATH 被覆盖,ls、vi 都无法执行
漏掉 $PATH 后连基本命令都调不到,只能重新登录或临时补回默认 PATH 才能修复。
验证:
which psql pg_ctl initdb
psql --version
预期三条都指向 /usr/local/pgsql/bin/,且不带全路径能执行出版本号。
八、启动与验证
su postgres -c "/usr/local/pgsql/bin/pg_ctl -D /data/pgdata -l /data/pgdata/logfile start"
预期:
waiting for server to start.... done
server started
验证三连:
su postgres -c "/usr/local/pgsql/bin/pg_ctl -D /data/pgdata status"
su postgres -c "/usr/local/pgsql/bin/psql -c 'select version();'"
ss -lntp | grep 5432
预期:
pg_ctl: server is running (PID: 12345)
PostgreSQL 14.24 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514, 64-bit
LISTEN 0 244 127.0.0.1:5432 0.0.0.0:*
三条都对,说明实例已可用。
停止:
su postgres -c "/usr/local/pgsql/bin/pg_ctl -D /data/pgdata stop"
预期 waiting for server to shut down.... done 与 server stopped。
8.1 关于开机自启
源码编译装出来的实例没有开机自启 ,也没建 systemd 单元。服务器重启后数据库是停的,需要手动 pg_ctl start。
要在生产环境上做成服务,新建单元文件:
cat > /etc/systemd/system/postgresql.service <<'EOF'
[Unit]
Description=PostgreSQL database server
After=network.target
[Service]
Type=forking
User=postgres
Group=postgres
Environment=PGDATA=/data/pgdata
ExecStart=/usr/local/pgsql/bin/pg_ctl -D /data/pgdata -l /data/pgdata/logfile start
ExecStop=/usr/local/pgsql/bin/pg_ctl -D /data/pgdata stop
ExecReload=/usr/local/pgsql/bin/pg_ctl -D /data/pgdata reload
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now postgresql.service
systemctl status postgresql.service
注意 Type=forking:pg_ctl start 是 fork 出后台进程后自己退出,如果写成 Type=simple,systemd 会以为服务已退出而反复重启。
如果实例已用 pg_ctl 启动过,先 pg_ctl stop 再 systemctl start,否则 systemd 找不到它派生的进程,状态显示 inactive 但端口实际在监听,容易误判。
不要混用两种启停方式。用 systemctl 起的就用 systemctl 停,用 pg_ctl 起的就用 pg_ctl 停。混用会导致 systemd 记录的状态与实际不符。
九、十个常见问题
9.1 configure 报 no acceptable C compiler found in $PATH
缺 gcc。RHEL 8 最小化安装不含编译器。
dnf install -y gcc
离线环境先按第 2.2 节挂 ISO 配本地源。装完不需要清理,./configure 失败不留残件,直接重跑即可。
9.2 mount /dev/sr0 报「找不到媒体」
虚拟化平台的光驱没有连接介质。在 vSphere 或类似平台上打开虚拟机设置,选择 ISO 文件后勾选「已连接」(Connected),只挂载不连接是识别不到的。
lsblk 显示 /dev/sr0 的 SIZE 为 0 或 1024 都不代表没介质,用 blkid /dev/sr0 确认:
blkid /dev/sr0
# 有介质时输出类似:/dev/sr0: UUID="2023-11-08-..." LABEL="RHEL-8-10-0-BaseOS-x86_64" TYPE="iso9660"
9.3 装完 psql 提示 command not found
两种可能:PATH 没配,或 PATH 配错路径。
echo $PATH
which psql
ls /usr/local/pgsql/bin/psql
如果 ls 能看到文件而 which 找不到,就是 PATH 问题。检查 /etc/profile.d/pgsql.sh 内容,改完记得 source /etc/profile。
已经开着的终端不会自动加载新配置,改完必须 source 或重新登录,否则会误判成配置没生效。
9.4 报错 FATAL: role "root" does not exist
用 root 身份连库时出现。PostgreSQL 本地 socket 连接默认把当前系统用户名当作数据库角色名,root 不是数据库里的角色。
两个解法:
# 解法一:显式指定角色
psql -U postgres -c "select version();"
# 解法二:切到 postgres 用户再执行
su postgres
psql -c "select version();"
所有 psql、pg_dump、pg_dumpall 命令都建议带 -U postgres,不受当前登录身份影响。
9.5 报错 FATAL: could not create shared memory segment
共享内存不足,多为 shared_buffers 设置过大,或容器环境限制了 shm 大小。
ipcs -lm | head -5
sysctl kernel.shmmax kernel.shmall
df -h /dev/shm
容器环境中 --shm-size 默认 64M,需要启动时调大:--shm-size=1g。物理机或虚拟机上调 kernel.shmmax 即可。
9.6 启动报错 could not open file ".../timezone/Asia/Shanghai"
时区文件属主不是 postgres。典型成因是以 root 身份执行了 make install,文件属主变成 root。
find / -name "timezone" -type d 2>/dev/null
chown -R postgres:postgres /usr/local/pgsql/share/timezone
chown -R postgres:postgres /usr/local/pgsql/share/timezonesets
用 -R 递归修正,只改目录本身不够,里面按洲和城市分层的文件仍是 root 属主。
9.7 initdb 报 permission denied
数据目录属主不对或权限过宽。
ls -ld /data/pgdata
chown postgres:postgres /data/pgdata
chmod 700 /data/pgdata
权限必须是 700,755 或 775 都会被拒绝。
9.8 psql 里上下键出现 ^[[A
readline 没编进去。检查宏:
grep HAVE_LIBREADLINE /usr/local/pgsql/include/pg_config.h 2>/dev/null || \
grep HAVE_LIBREADLINE src/include/pg_config.h
如果是 /* #undef HAVE_LIBREADLINE */,说明编译时缺 readline-devel。装上后必须 make distclean 清除再重新 configure,直接重跑 configure 可能沿用之前的缓存结果。
9.9 服务器重启后数据库没起来
源码编译的实例没有开机自启。配置 systemd 单元,见 8.1 节。临时先手动起:
su postgres -c "/usr/local/pgsql/bin/pg_ctl -D /data/pgdata -l /data/pgdata/logfile start"
养成习惯:每次登录服务器先查状态。
su postgres -c "/usr/local/pgsql/bin/pg_ctl -D /data/pgdata status"
9.10 编译退出码非 0,报错看不懂
先筛出报错行:
make -j$(nproc) 2>&1 | grep -n "error:" | head -20
高频原因:
| 报错关键字 | 原因 | 处理 |
|---|---|---|
cannot find -lreadline |
缺 readline-devel | 装上后 make distclean 重来 |
cannot find -lz |
缺 zlib-devel | 同上 |
No space left on device |
磁盘满 | 清理后重来 |
fatal error: openssl/ssl.h |
缺 openssl-devel 但带了 --with-openssl | 装依赖或去掉该参数 |
| Killed | 内存不足被 OOM | 去掉 -j,改单线程编译 |
十、验证清单
安装完成后逐项确认,全部通过才算可用。
# 1. 程序版本
/usr/local/pgsql/bin/postgres --version
# 2. 已编入的关键功能
grep -E "HAVE_LIBREADLINE|HAVE_LIBZ" /usr/local/pgsql/include/pg_config.h
# 3. 共享库已登记
ldconfig -p | grep libpq
# 4. 实例运行中
su postgres -c "/usr/local/pgsql/bin/pg_ctl -D /data/pgdata status"
# 5. 服务端版本
su postgres -c "/usr/local/pgsql/bin/psql -c 'select version();'"
# 6. 端口监听
ss -lntp | grep 5432
# 7. 数据目录权限
ls -ld /data/pgdata
# 8. 时区文件属主
ls -ld /usr/local/pgsql/share/timezone
八项全过,环境可用。
写在最后
源码编译安装 PostgreSQL 本身的命令不多,出问题基本集中在四处:离线环境的依赖、configure 参数含义、PATH 配置、文件属主。
四个坑各记一句话:
- 离线装不了包,先挂 ISO 做本地源
--datadir是只读数据目录,不是 PGDATA- PATH 指向
$prefix/bin,不是源码解压目录,且不能漏:$PATH - 属于 postgres 的文件,属主就得是 postgres
装完之后如果要升级到新版本,可以看另一篇《PostgreSQL 小版本升级实战》,讲的是同大版本内替换二进制的完整流程和几个更隐蔽的坑。