
不到万不得已没人愿意在一台新机器上装一个已经停止维护的数据库。但我上个月刚接了一个这样的活儿某老客户的业务系统跑在 PostgreSQL 9.6 上服务器是 CentOS 7.9因为业务代码里依赖了 9.6 的某些老行为加上线上数据文件格式就是 9.6 的整体升级不是他们现阶段能接受的。我只能在一台全新 CentOS 7.9 上把 PostgreSQL 9.6 完整复刻出来再把旧数据导进去。这篇文章就把整套流程写清楚尤其是那些光看官方文档根本注意不到的坑。先说结论装 PostgreSQL 9.6 这个操作本身不难难的是你大概率会在源选择、初始化、启动这几个环节被卡住。我把实际踩过的坑和排查思路都整理在下面了。1. 装之前先确认CentOS 7 自带的 PostgreSQL 不是 9.6别等装完才发现1.1 系统源里的“惊喜”版本很多人拿到一台 CentOS 7.9第一反应就是yum install postgresql-server。这个命令一定能装成功但装出来的是 PostgreSQL9.2.24不是 9.6。CentOS 7 自带 base 仓库里维护的 PostgreSQL 版本就是 9.2.x9.6 从来就没有进过 CentOS 官方源。我当时第一次在 7.9 上装数据库时也吃过这个亏装完psql --version一看版本直接愣住。所以这里请大家务必记住只要标题是“CentOS 7.9 安装 PostgreSQL 9.6”就不要碰系统默认的 postgresql 相关包。先看一眼当前源里有什么yum list available postgresql* yum info postgresql-server结果会很清楚postgresql-server.x86_64 9.2.24。版本号明明白白除非你就要 9.2否则别装。1.2 什么情况下才需要 9.6什么情况下不建议先帮大家分个类别盲目跟风装老版本。如果你是新项目、新环境没有任何历史包袱请直接安装仍在维护的 PostgreSQL 14、15 或更高版本。9.6 早在 2021 年 11 月就停止支持了不再有安全补丁也不再有 bug 修复长期用它等于把一个有已知漏洞的数据库暴露在业务里。需要装 9.6 的典型场景是下面这几类老系统替换服务器原机器跑的是 9.6应用代码里用到了pg_stat_activity某些字段、plpgsql 锁行为等新版本已经变化的东西整个升级回归成本太高。旧备份文件恢复pg_dump导出的 9.6 归档文件-Fc格式固然可以用新版本恢复但结构、权限、扩展版本可能触发一系列兼容问题迁移窗口又很短不如原地复刻一个 9.6 实例。第三方系统强约束某些商业系统对数据库版本有硬性要求比如只认证 9.6你在实施阶段没得选。装之前务必把“这是临时过渡方案”这个判断明确告诉业务方。我是直接跟客户写明9.6 只作为过渡期兼容环境后续还是要排期升级高版本。1.3 动手前的环境自检清单确认环境不会给自己挖坑我习惯先执行一轮基础检查cat /etc/redhat-release uname -m free -g getenforce systemctl status firewalld重点是架构x86_64 还是 aarch64、内存大小、SELinux 状态和防火墙状态。后面每一步排查都跟这几个信息相关。内存如果只有 1G9.6 默认参数跑起来会很吃力建议先想好要不要调shared_buffers。2. 配置 PGDG 软件源官方源才是 9.6 的真正来源附带依赖坑提示2.1 为什么必须用 PGDG 源PostgreSQL 官方有一个面向 Red Hat 系发行版的 YUM 仓库叫 PGDGPostgreSQL Global Development Group 的软件源里面维护了从 9.6 到 16 的所有历史版本 rpm 包。CentOS 7 完全兼容这些 rhel 7 的包所以装 9.6 的唯一正确来源就是 PGDG。安装仓库 rpm 的方式很直接用的也是官方通用地址yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm如果这个链接因为时间推移失效就去download.postgresql.org/pub/repos/yum/reporpms/目录下找EL-7-x86_64对应的最新包规则是一样的。装完以后/etc/yum.repos.d/下会出现pgdg-redhat.repo之类的配置文件。2.2 仓库启用逻辑别让 yum 装错版本我见过不少人在装完 PGDG 源后直接执行yum install postgresql96-server结果提示找不到包或者更离谱的是装成了系统源的 9.2。原因出在 repo 文件的启用状态上。打开/etc/yum.repos.d/pgdg-redhat.repo看里面通常有一整段比如[pgdg96]、[pgdg14]、[pgdg15]每个段落的enable值可能是不一样的。你需要的那个pgdg96段不一定是默认启用状态。稳妥的做法是安装时显式指定仓库yum --enablerepopgdg96 install -y postgresql96-server postgresql96如果你希望之后所有 PG 相关操作都默认走这个源也可以把 repo 文件里其他版本的enable0把pgdg96的enable1。不过我个人更推荐每次安装都带--enablerepo参数这样不会影响其他源的默认状态。2.3 GPG key 没导入的报错第一次通过 PGDG 源安装时大概率你会碰到这个提示Public key for postgresql96-9.6.24-1PGDG.rhel7.x86_64.rpm is not installed这不是源有问题而是仓库 rpm 自带的 GPG key 还没有被 rpm 数据库收录。解决办法是手动导入rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-PGDG*然后重新执行安装命令。这里提醒一句绝对不要用--nogpgcheck跳过校验。数据库这种核心组件包完整性校验是完全不能省的。2.4 依赖缺失问题PGDG 仓库里的 9.6 依赖一般比较老但在 CentOS 7.9 上通常都能顺利解决因为 7.9 的库版本本身不算新。我实际操作中偶尔会遇到perl相关模块缺失的情况这种多半是系统太“瘦”了。此时先补一个 EPEL 源再重试yum install -y epel-release yum --enablerepopgdg96 install -y postgresql96-server postgresql96如果还报依赖错误就老老实实看缺哪个包、从哪个源能装。千万不要用rpm -ivh --nodeps这种暴力方式绕过去否则后面initdb或者服务启动时会在莫名其妙的地方崩掉排查成本高得多。3. 安装主程序和数据库初始化目录结构跟旧版本不一样别找错文件3.1 安装并确认版本仓库配置好以后安装就是一条命令的事yum --enablerepopgdg96 install -y postgresql96-server postgresql96装完先验证版本不要直接开始初始化/usr/pgsql-9.6/bin/postgres --version正常会输出postgres (PostgreSQL) 9.6.24之类的结果。你注意看路径9.6 的程序目录是/usr/pgsql-9.6不是/usr/bin。这也是很多教程没强调的地方9.6 版本的程序、工具都集中在带版本号的目录下psql、pg_dump、initdb都在/usr/pgsql-9.6/bin/下。3.2 数据目录为什么在 /var/lib/pgsql/9.6/dataCentOS 下安装 PostgreSQL无论哪个版本数据目录默认都在/var/lib/pgsql/下但 9.6 的路径里面多了一层版本号程序目录/usr/pgsql-9.6/bin/数据目录/var/lib/pgsql/9.6/data/配置文件/var/lib/pgsql/9.6/data/postgresql.conf认证配置/var/lib/pgsql/9.6/data/pg_hba.conf这意味着你千万不要去/etc/postgresql/或者/var/lib/pgsql/data/找配置文件这是新手最容易卡住的地方。我见过有人在/var/lib/pgsql/data找半天最后才发现真正的数据目录多了个版本号。3.3 初始化命令为什么是 postgresql96-setup初始化数据库这一步9.6 提供的是一个跟 systemd 紧密配合的脚本/usr/pgsql-9.6/bin/postgresql96-setup initdb执行后如果一切正常末尾会看到类似Success. You can now start the database server的提示。这个脚本做了什么它会检查数据目录是否存在、目录属主是否为空、必要时创建postgres系统用户并且把初始化参数按 systemd unit 文件的预期来设置。所以我建议直接用脚本而不是自己手动initdb。有人可能会问我能不能自己指定字符集可以但没必要折腾。CentOS 7 默认 locale 一般是en_US.UTF-8初始化出来的数据库编码就是 UTF8足够满足绝大多数场景。如果你明确需要指定su - postgres -c /usr/pgsql-9.6/bin/initdb -D /var/lib/pgsql/9.6/data --encodingUTF8 --localeen_US.UTF-8自己手动 initdb 不是不行但要注意后面 systemd 的PGDATA环境变量必须跟实际路径一致。用官方脚本就完全不用操心这个对齐问题。3.4 数据目录属主问题初始化脚本一般会把/var/lib/pgsql/9.6/data的属主搞对但如果你是手动创建的父目录或者从别的机器拷贝了数据目录过来这一步很容易翻车。检查一下ls -ld /var/lib/pgsql/9.6/data属主应该是postgres:postgres。如果不是启动时 9.6 会直接报could not open directory之类的权限错误。手动修正chown -R postgres:postgres /var/lib/pgsql/9.6这条命令要在 root 下执行并且最好在 initdb 之前就处理好。4. 启动服务与开机自启服务名是 postgresql-9.6以及一次启动失败的完整排查4.1 服务名容易打错9.6 的 systemd 服务名是postgresql-9.6注意中间有版本号不是postgresql96更不是postgresql。正确的启动方式systemctl start postgresql-9.6 systemctl enable postgresql-9.6 systemctl status postgresql-9.6如果你执行systemctl start postgresql-9.6时提示找不到服务大概率是第二步初始化initdb没有执行成功systemd 找不到对应的配置。此时回到上一节把初始化脚本重新跑一遍。4.2 一次典型的启动失败排查链路我在这台 7.9 上安装时第一次systemctl start就翻车了。systemctl status postgresql-9.6显示inactive (dead)没有任何有效信息。这里分享我的完整排错思路比直接告诉你怎么解决更实用。第一步看 systemd 日志journalctl -u postgresql-9.6 -n 100 --no-pager如果日志里什么都没有或者只有模糊的 kill 信息第二步就要看 PostgreSQL 自己的日志ls -lt /var/lib/pgsql/9.6/data/pg_log/9.6 默认会在数据目录下开pg_log文件夹存放运行日志启动失败的具体原因都在这里。第三步如果还看不出来看 SELinux 审计日志ausearch -m avc -ts recent我这次碰到的实际问题journal 里只有一句Unit postgresql-9.6.service entered failed state但pg_log里根本没有日志文件后来用ausearch才发现是 SELinux 拦截了进程对数据目录的访问。SELinux 默认策略下PostgreSQL 数据目录的标签如果不对启动时进程会被直接拒绝。修复方式也很直接restorecon -Rv /var/lib/pgsql/9.6如果你的数据目录是从别处拷贝过来的这个命令基本每次都能救你。这个坑非常隐蔽网上教程很少提到因为大多数博客是在 SELinux 关闭的环境下写的。4.3 常见启动失败原因速查表把这几年遇到过的启动问题整理成一张表方便大家对着排查报错关键字原因处理方式data directory ... does not exist没有 initdb 或 PGDATA 路径不对检查环境变量执行 postgresql96-setup initdbcould not create shared memory segment共享内存参数问题检查/dev/shm大小调整shared_buffersPermission denied且 AVC 有记录SELinux 拦截restorecon -Rv /var/lib/pgsql/9.6postmaster.pid相关报错上次非正常关闭残留确认无进程后删除postmaster.pidNo usable systemd notify support系统目录权限问题检查/var/lib/pgsql 属主could not bind to 127.0.0.1端口被占用ss -lntp查看端口占用4.4 启动成功后的验证动作服务起来后别急着高兴先做两个验证。一个是看端口ss -lntp | grep 5432另一个是用pg_isready探活/usr/pgsql-9.6/bin/pg_isready -h 127.0.0.1 -p 5432返回accepting connections才算真正 OK。systemctl enable postgresql-9.6设置完开机自启后建议直接重启一次服务器验证服务能自动拉起。这一步很多人偷懒不做等到客户那边断电重启才发现服务没起来。5. 安全配置与远程访问密码、监听、防火墙、SELinux 一次说清楚5.1 设置 postgres 用户密码数据库实例刚初始化完本地通过peer认证可以免密登录sudo -u postgres psql登录后要立刻设置密码ALTER USER postgres PASSWORD 你的强密码;这里有一个 9.6 级别的关键认知9.6 只支持 md5 认证方式不支持scram-sha-256。scram-sha-256是 10 版本才引入的。所以写pg_hba.conf时认证方法只能写md5。很多从高版本转回来的人在这里会踩坑写了scram-sha-256导致连接永远失败。5.2 创建业务账号和数据库生产环境不建议直接用postgres超级用户跑业务。创建一个独立账号把权限控制在业务库内CREATE ROLE app_user LOGIN PASSWORD app_pass; CREATE DATABASE app_db OWNER app_user ENCODING UTF8;如果业务项目用了扩展也要提前在目标库里创建\c app_db CREATE EXTENSION IF NOT EXISTS uuid-ossp; CREATE EXTENSION IF NOT EXISTS pg_trgm;扩展是按库存储的不是全局的。在postgres库里创建完换到业务库还要再建一次。这也是个容易忘记的细节。5.3 配置 pg_hba.conf顺序很重要编辑/var/lib/pgsql/9.6/data/pg_hba.conf这是远程连接成败的关键文件。默认配置里本地连接是peer远程 host 连接是ident。ident认证对大多数应用来说不是你要的所以必须改成md5。我常用的配置片段# 保留本地超级用户 peer 认证 local all postgres peer # 本机回环访问用 md5 host all all 127.0.0.1/32 md5 # 内网网段访问用 md5 host all all 192.168.10.0/24 md5pg_hba.conf的匹配规则是从上到下匹配第一条所以更宽松的规则要放到更下面更精确的规则放上面。比如你只让内网访问就不要写all直接把网段写死。假如你想限制某个应用账号只能连某个库可以写host app_db app_user 192.168.10.5/32 md55.4 修改 postgresql.conf 开启监听默认情况下 PostgreSQL 只监听localhost要对外提供服务必须修改listen_addresses * port 5432postgresql.conf同样在数据目录下/var/lib/pgsql/9.6/data/postgresql.conf。修改后有两种生效方式systemctl reload postgresql-9.6reload不需要重启对已有连接影响最小。如果你改了listen_addresses以外的核心参数比如shared_buffers那就要systemctl restart postgresql-9.6才能完整生效。这里我建议第一次修改时直接重启避免 reload 后配置生效不完整的错觉。5.5 防火墙放行CentOS 7 默认使用 firewalld。放行 PostgreSQL 有两种方式第一种是按服务名firewall-cmd --permanent --add-servicepostgresql firewall-cmd --reload第二种是按端口更直观firewall-cmd --permanent --add-port5432/tcp firewall-cmd --reload检查放行结果firewall-cmd --list-all注意如果服务器上跑的是自定义 iptables 服务而非 firewalld那还要单独处理 iptables 规则。用systemctl status firewalld看下防火墙用的哪个别改了半天没生效才发现。5.6 SELinux默认端口没事改端口就会卡你CentOS 7 默认 SELinux 是 enforcing 状态。PostgreSQL 的默认端口 5432 在 SELinux 策略里属于postgresql_port_t不需要额外放行。但如果你把端口改成 5444 之类的自定义端口SELinux 会直接拦截远程连接而且本地psql可能一切正常从外部连就是超时。这种情况要给新端口加标签semanage port -a -t postgresql_port_t -p tcp 5444如果系统没有semanage先装yum install -y policycoreutils-python判断是不是 SELinux 的问题用ausearch -m avc -ts recent命中postgresql_port_t相关的记录基本就是它。5.7 远程连接验证配置完成后从客户端执行psql -h 服务器IP -p 5432 -U app_user -d app_db如果连不上按这个顺序排查ss -lntp | grep 5432确认 postgres 进程真的监听在0.0.0.0:5432或具体网卡上。telnet 服务器IP 5432确认 TCP 层是否可达。ausearch -m avc -ts recent确认 SELinux 有没有拦截。再次确认pg_hba.conf里 host 记录的顺序和认证方式。大部分远程连接失败最后都能落到pg_hba.conf写错或者防火墙没放行这两个原因上。6. 9.6 的老版本脾气数据备份、后续升级路径与运维提醒6.1 备份习惯必须比新版本更严9.6 已经停止维护这意味着它不会有安全补丁。越是这样备份就越重要。最基本的逻辑备份命令/usr/pgsql-9.6/bin/pg_dump -U app_user -F c -d app_db -f /backup/app_db.dump-F c是自定义格式配合pg_restore恢复时非常灵活。我习惯再加一个定时任务0 2 * * * /usr/pgsql-9.6/bin/pg_dump -U app_user -F c app_db /backup/app_$(date \%F).dump除了逻辑备份物理备份也别忽略。最简单可靠的办法是停库后打包数据目录systemctl stop postgresql-9.6 tar -czf /backup/pgdata_$(date %F).tar.gz /var/lib/pgsql/9.6/data systemctl start postgresql-9.6对于 9.6 这种老版本我建议逻辑备份和物理备份同时保留恢复时多一条路。6.2 升级路线最好别一把梭9.6 的数据不能一直趴在老版本上迟早要升级。升级路线我先劝一句不要在生产环境直接尝试跨多个大版本的pg_upgrade。9.6 到 14 以上跨度太大二进制文件格式和系统表结构变化都不小虽然工具理论上支持但出问题的概率和排查成本都很高。稳妥路线是pg_dump全量备份数据。在新服务器上安装目标版本如 14 或 15初始化全新实例。pg_restore恢复数据。业务侧做完整回归测试。切换连接串。这条路线看起来多花一点时间实际上最省心。唯一要注意的是老库里的某些扩展比如uuid-ossp在新版本里可能已经被并入pgcrypto或其他模块恢复时要提前处理。6.3 老版本环境的一些基础参数建议安装和启动都搞定后我根据经验还会顺手做几件事把postgresql.conf里的timezone显式设为Asia/Shanghai避免系统时区变更影响业务。根据内存调整shared_buffers一般取物理内存的 25% 左右别直接跑默认值9.6 默认配置对 4G 以上内存的机器来说太保守。如果并发连接多建议引入连接池比如常见的 PgBouncer9.6 的max_connections默认只有 100扛不住大量短连接。把log_line_prefix调成带时间戳的格式方便排查问题log_line_prefix %m [%p] %q%u%d 这些调整虽然不能改变 9.6 老版本的事实但能让它在一台新机器上跑得稳定些。最后再分享一点个人体会搞定这台 9.6 之后我让同事把整个操作流程和踩坑记录整理成了内部文档第二台同配置服务器半小时就装完了。老版本就是这样第一台总是最痛苦的但只要你把源、目录结构、SELinux 这几个关键点摸透后面就是无脑复制。整体上来讲9.6 能不用就别用但既然业务需要那就把每一步做扎实同时把升级计划排上日程别让业务一直趴在停止维护的数据库上。