我接手过一套还跑在 PostgreSQL 9.6.10 上的老业务系统。线上库里存着好几年的业务数据,平时没人敢碰;可真要调试问题、做数据修复、验证新逻辑的时候,麻烦就来了——你不可能拿生产库做实验。于是本地 Windows 机器上必须搭一个同样版本的 PostgreSQL 9.6.10 开发环境。刚开始我以为装个数据库哪有什么难度,结果在 Windows 下真正把它跑顺,前前后后踩了不少坑:服务起不来、端口被占、pg_hba.conf 写错、中文 locale 捣乱……这篇就围绕 PostgreSQL 9.6.10 在 Windows 下的安装和调试,把我从头到尾的做法、判断逻辑和排障顺序完整写一遍,适合需要本地复现老版本环境、或者正在被数据库连接问题折磨的开发者参考。
1. 为什么还在用 9.6.10:别只盯着“旧版本”三个字
1.1 9.6.10 的版本坐标
很多新手看到 9.6.10 的第一反应是“这不是五六年前的东西了吗”。这种想法没错,但“旧”不代表“没有人在用”。9.6 系列从 2016 年开始发布,9.6.10 是它生命周期里的一个维护版本,在它之后的 10、11、12、13、14、15 甚至 16,每代都带来了新特性,可现实是大量业务系统还稳定跑在 9.6 上,尤其是部署时间比较早、后续一直靠维护续命的老系统。
我需要强调一个容易被忽略的点:本地开发环境的版本号,最好跟生产环境做到大版本和小版本完全一致。大版本决定了 SQL 语法、优化器行为、系统表结构,小版本则包含了一堆 bug 修复,不同小版本的细微行为差异在特定场景下也足够让人头疼。你如果本地装的是 15,线上是 9.6.10,查一个执行计划、测一个函数返回结果、验证一个批量导入脚本,都可能出现“本地正常、线上异常”的诡异情况。所以这台 Windows 开发机,就必须老老实实装 9.6.10。
1.2 本地环境与生产环境对齐带来的实际价值
做版本对齐不是强迫症,而是调试效率问题。我举几个实际场景:
- 线上库出现死锁或者慢查询,你需要在本地用同版本库复现相同的执行计划,才能定位是索引失效还是统计信息问题;
- 老应用的某个扩展或者存储过程只兼容 9.6,新版本根本不支持;
- 要评估升级到新版本之前,你得先在一模一样的环境里跑一遍兼容性测试;
- 数据修复脚本涉及系统表操作,9.6 的系统表结构和 10 以后差异很大,照着新版本文档写脚本往往直接废掉。
另外要清楚,9.6 和 10、11 以后版本有几项关键差异。认证上,9.6 默认使用 md5,而新版本默认是 scram-sha-256;9.6 没有声明式分区,没有 identity 列,并行查询能力也远不如后面几个版本。这些差别不亲自跑一遍,很容易在排查线上问题的过程中产生严重误判。
所以,结论很简单:别管它多老,只要线上是它,本地就得是它。
2. 安装向导逐页拆解:每个选项都别默认到底
2.1 下载、组件与目录规划
先在下载页面找到 Windows x86-64 对应的 9.6.10 安装包。下载后右键选择“以管理员身份运行”,这一步在 Windows 10/11 上尤其重要,否则后续写注册表、创建 Windows 服务都可能报权限不足。
安装向导第一步会让你选安装目录。默认是C:\Program Files\PostgreSQL\9.6,我的建议是改到空间充裕的独立分区,比如D:\PostgreSQL\9.6。这样做的原因很简单:这个目录里装着可执行文件、共享库和一部分配置文件,如果哪天 C 盘满了或者系统还原出问题,重装系统也不会直接干掉数据库程序本体。更重要的是,安装目录和你下面的数据目录一定要分开,程序归程序,数据归数据。
组件选择页面里,默认会勾选 PostgreSQL Server、pgAdmin 4、Stack Builder 等。我的做法是:PostgreSQL Server 和 Command Line Tools 必选,pgAdmin 4 可以留着备用,但 Stack Builder 一定去掉。这是个容易忽视的坑——Stack Builder 是给后续装扩展、驱动用的辅助工具,早期版本它总在你不需要的时候跳出来提示更新,实际用处不大,装完还占用一大堆启动项。在纯粹的命令行工作流里,它属于可以完全绕开的东西。
2.2 数据目录、超级用户密码和端口
安装向导中“Data Directory”一页,我填的是D:\pgdata\9.6。这里需要特别注意:数据目录和安装目录分开,带来的好处很直接。
- 数据库实例升级时,可以只处理数据目录,不必连带程序目录一起纠结;
- 做备份时,直接拷贝数据目录就能得到一个冷备份;
- 以后要装第二个 PostgreSQL 实例,数据目录互相独立,互不干扰。
接下来是超级用户密码。这个密码同时会被安装器用来创建 Windows 下的 postgres 服务账户。也就是说,安装完成后,你客户端连接数据库要用的 postgres 用户密码就是这一行。建议记到密码管理器里,这个密码丢了,在 Windows 上重置比 Linux 麻烦得多。
端口默认是 5432。如果本机之前装过其他 PostgreSQL 实例,或者某进程已经占用 5432,安装器在创建服务时不会帮你检查端口是否干净,最终表现就是服务启动失败。这块我强烈建议在安装前先执行一条命令看一眼:
netstat -ano | findstr 5432如果有输出,说明端口已占用,那就直接安装时改成 5433 或 5434,别跟现有进程硬碰。
2.3 Locale 选择:Windows 中文环境最容易埋雷的地方
安装向导里有一页叫 Locale,这大概是整个安装过程中最容易被忽略、后果却最严重的选项。默认情况下,Windows 安装包会跟随系统区域,中文系统通常对应Chinese (Simplified)_China.936。
这个值在后续会直接写入数据库的lc_collate和lc_ctype,影响排序规则、大小写比较、甚至部分字符串函数的行为。等你建库、导数据、发现中文排序结果跟生产环境完全不一样的时候,再想改就晚了——lc_collate和lc_ctype在 initdb 之后基本固化,想换只能重建整个数据库集群。
我的建议很简单:本地开发调试环境,Locale 直接选 C。如果你确实需要其他排序行为,再考虑English_United States.1252。对大多数老系统兼容性测试来说,C是最省事、最不容易出幺蛾子的选择,字符集则配合编码设置为 UTF8。
有一个概念必须说清楚:Locale 和 encoding 是两回事。encoding 决定字符怎么存,Locale 决定字符串怎么比较和排序。很多人看到 server_encoding 是 UTF8 就觉得万事大吉,却忽略了 lc_collate 是中文 Windows 的默认值,这就是后面各种诡异排序问题的根源。
2.4 安装过程常见的失败现场
安装到“服务创建”这一步挂掉,是 Windows 下最常见的事故现场。日志往往只给你一句“服务 postgresql-x64-9.6 未能启动”,让人一头雾水。我遇到过的原因大致有这几类:
第一,数据目录残留。如果安装前曾经手动建过D:\pgdata\9.6这个目录,里面还有旧文件,initdb 初始化新实例时会直接拒绝操作,因为没有清空目录。解决方法是把目录先移走改名,再让安装器新建一个干净的目录。
第二,安全软件拦截。安装器要创建 Windows 服务并写入注册表,某些安全软件会把这个动作当成恶意行为拦下来。表现为安装过程看起来正常,但最后服务创建失败或系统服务列表里根本找不到该项。建议安装前临时关闭实时防护,装完再开,或者安装后手动把 PostgreSQL 的相关目录加入白名单。
第三,旧实例残留。如果之前卸载过 PostgreSQL 但没卸干净,Windows 服务列表里可能残留一个同名服务,此时新安装器不会自动覆盖,最终表现就是新装程序无法使用。管理员权限的 cmd 窗口里可以做两件事:查服务状态,以及确认残留再决定是否手动清理服务项。
3. 安装完成后的三件套验证:服务、日志、命令行
3.1 Windows 服务状态与监听检查
安装完成的窗口一关,别急着连数据库,先按顺序做三步检查。
第一步,打开“服务”管理窗口,找到postgresql-x64-9.6,看状态。如果显示“正在运行”,那是好事;如果是“已停止”,右键点启动,观察会不会报错。
第二步,用命令行验证得更准确:
sc query postgresql-x64-9.6看输出里的STATE那一行,如果是RUNNING,说明 Windows 服务层面是正常的。注意服务名里 x64 对应 64 位安装包,32 位包可能叫postgresql-9.6,具体情况以你机器上实际安装为准。
第三步,确认端口监听:
netstat -ano | findstr 5432如果你看到类似TCP 0.0.0.0:5432 LISTENING的记录,说明 postgres.exe 已经真实监听了端口。这一步特别重要的原因是:Windows 服务状态为“运行”不一定等于数据库在正常工作,服务进程可能因为初始化失败反复重启,也可能因为配置文件错误根本没有绑定端口。服务、进程、监听这三层,每一层都要确认。
3.2 psql 连接验证
在命令行里进入安装目录下的 bin 目录,然后执行:
cd D:\PostgreSQL\9.6\bin psql -U postgres -h 127.0.0.1 -p 5432 -d postgres这里有个细节:-h参数指定的是主机地址,写localhost和写127.0.0.1在某些系统上会有细微差别——localhost可能解析到 IPv6 的::1,如果 PostgreSQL 只监听了 IPv4 就会连接失败。所以在排查阶段,我习惯先写127.0.0.1做最小化验证,排除掉 DNS 和 IPv6 这一层干扰。
输入密码进入 psql 后,执行这几条命令作为验货:
SELECT version(); SHOW server_encoding; SHOW lc_collate; SHOW lc_ctype;看到版本号是 9.6.10,server_encoding 是 UTF8,lc_collate 和 lc_ctype 是 C,那么安装这步基本就是干净的。如果这里的 lc_collate 显示成 Chinese 之类,那就要回到 2.3 节,准备重建实例了。
3.3 日志文件定位与快速解读
Windows 事件查看器对 PostgreSQL 的意义有限,它只记录“服务启动了/停止了”这种粗粒度信息,真正的数据库错误还是在 PostgreSQL 自己的日志里。
9.6 在 Windows 下默认启用了日志收集,日志在数据目录下的pg_log子目录里,文件名长这样:postgresql-2025-01-15_103000.log。很多第一次接触的人会到处找日志文件,其实就是这一个目录。每当服务启动失败、连接被莫名断开、崩溃重启之后,第一个动作应该是打开这个目录下最新的日志文件,看最后几十行。
常见关键信息包括:
FATAL: could not create listen socket for "localhost",说明端口绑定失败;Permission denied,大概率是数据目录的权限或 ACL 异常;database "xxx" does not exist,说明你连接的库名写错了或者还没建库;invalid value for parameter,说明 postgresql.conf 里的某项参数写得不合法。
日志分析能力是整个调试链路里最值钱的一环。建立“出错先看日志”的肌肉记忆,能省掉大量瞎猜的时间。
4. 调试实录:三个高频故障的完整排查链路
4.1 服务起不来的排障顺序
我在 Windows 上遇到的第一个硬骨头就是服务启动失败。现象是服务管理窗口里点“启动”,状态转了几个圈又回到“已停止”,偶尔弹一个错误码。这时候不要慌,按顺序排查。
第一,去看最新日志里最后几行。比如下面这种:
2025-01-15 10:30:01 CST FATAL: could not create listen socket for "localhost" 2025-01-15 10:30:01 CST HINT: bind() to port 5432 failed: Permission denied这个错误已经说得很明白了,端口 5432 被其他东西占了。怎么确认是谁?两条命令连起来用:
netstat -ano | findstr 5432 tasklist | findstr <pid>如果发现占用的是一个旧的 postgres.exe,那大概率是另一套 PostgreSQL 实例还在跑。处理思路就两条:停掉旧进程,或者在新实例的配置文件里改端口。我更倾向于改端口,因为本地这种场景下谁也不想影响其他已经跑着的服务。
第二,如果日志里没有明确的 FATAL,但服务就是起不来,检查postmaster.pid文件。这个文件在数据目录下,记录着 postmaster 进程的 PID 等信息。如果系统崩溃、电源断电、或者任务管理器强杀了 postgres 进程,PID 文件可能残留下来,下次启动时 postmaster 检测到“疑似已有实例在运行”,会拒绝启动或者卡在初始化阶段。
处理这类残留前,必须先确认没有真的 postgres 进程在跑:
tasklist | findstr postgres确认进程列表里完全没有 postgres 之后,再手动删除数据目录下的postmaster.pid。注意不要在有进程运行时删,否则会破坏运行实例的状态文件。
第三,数据目录权限问题。Windows 的权限模型和 Linux 不太一样,数据目录由安装器创建时,ACL 通常是按服务账户来配置的。有时候你从别人那里拷来一个数据目录,或者改过目录权限、移动过位置,当前服务账户没有访问权限,启动就会在初始化阶段失败。日志里多半会出现could not open directory或者Permission denied相关条目。这种情况处理起来比较干脆:确认目录里没有必须保留的数据,就直接用 initdb 重建一个新数据目录;如果有数据要保留,那就先解决 ACL 授权,给服务运行账户授予数据目录的完全控制权限。
4.2 Connection refused 不等于服务没启动
“psql: could not connect to server: Connection refused”这句话,可能是 PostgreSQL 调试里最劝退人的报错。先要破一个误解:它不等于数据库没在运行,更多时候是“客户端到服务端的这条链路里有环节没打通”。
排查链路应该是:
先看监听在不在。执行
netstat -ano | findstr 5432,如果没有 LISTENING 记录,说明 postgres 没起来或者端口配得不对,回到 4.1 节。如果监听在,继续。看服务端绑定地址。执行:
SHOW listen_addresses;9.6 的默认值是localhost,意味着它只监听本地回环地址。这时候你从局域网另一台机器连它,或通过机器名访问,都会被拒绝。想支持局域网连接,需要改配置:
listen_addresses = '*'改完重启服务。注意这个参数不能靠pg_reload_conf()生效,必须重启实例。
- 看 pg_hba.conf 的认证规则。9.6 的 pg_hba.conf 在数据目录下,大致的规则格式如下:
# TYPE DATABASE USER ADDRESS METHOD host all all 127.0.0.1/32 md5 host all all ::1/128 md5 host all all 192.168.1.0/24 md5如果客户端 IP 不在任何一条规则覆盖范围内,即使数据库正常运行、端口监听正常,照样会被拒绝连接,而且错误信息可能就一句话 “no pg_hba.conf entry for host”。很多人在这一步就直接懵了。修改完 pg_hba.conf 之后,不需要重启服务,执行一次重载即可:
SELECT pg_reload_conf();或者用命令行方式:
pg_ctl reload -D D:\pgdata\9.6- 最后才轮到 Windows 防火墙。默认规则不会放行外部对 5432 端口的访问,开发机如果只在本机连,这一步跳过也行;要通过局域网连,用管理员 cmd 放行:
netsh advfirewall firewall add rule name="PostgreSQL 5432" dir=in action=allow protocol=TCP localport=5432这里有一条安全底线要反复强调:调试时绝不要为了省事把 pg_hba.conf 里的地址段改成0.0.0.0/0再用trust。开发环境即便只有你自己用,trust认证等于裸奔,某些数据误操作和恶意访问成本都低到离谱。放行局域网网段、使用 md5 密码认证,是底线。
4.3 中文 Windows 下的 locale 与编码陷阱
和前两个“起不来”“连不上”的问题不同,locale 问题带迷惑性,因为数据库能正常起来、连接也正常,数据甚至能查能导,但某些行为就是不对。
具体表现有这几种:
- 中文排序结果和 Linux 生产库不一致;
- 导入导出的中文文本在客户端显示乱码;
- 新建数据库时必须反复指定 ENCODING 和 LC_COLLATE,否则建库报错;
- 字符串比较行为不符合预期,比如大小写、空格处理差异。
根子都在安装时选的那个 Locale。中文 Windows 默认值对应Chinese (Simplified)_China.936,代码页 936 是 GBK 体系,和 Linux 上常见的 UTF8/C 组合行为差异很大。验证当前实例的 locale 状态很简单:
SHOW lc_collate; SHOW lc_ctype;判断标准是这样的:如果你想做一个“跟 Linux 生产环境行为一致的本地复现环境”,那lc_collate和lc_ctype至少不能走 Windows 代码页路线,最省心的是C。如果你发现已经是C,那就没问题;如果是中文系统默认值,而数据目录里还没什么有价值的内容,直接重建实例比硬撑着改参数靠谱得多。
重建实例的正确姿势是:确认服务已停止,把现有数据目录改名或移走,然后用 initdb 新建:
D:\PostgreSQL\9.6\bin\initdb.exe -D D:\pgdata\9.6 -E UTF8 --locale=C -U postgres执行完再启动服务。这样重建出来的实例,server_encoding 是 UTF8,lc_collate 和 lc_ctype 都是 C,和 Linux 上最常见部署配置基本对齐,后续调试省心一大截。
为什么这个坑特别值得刷出来单独讲?因为它在 Windows 上几乎必踩,而且在安装向导里只是一个不起眼的下拉框。装的时候随便下一步,后面要用好久来还债。
5. 顺手调优:让 9.6.10 在 Windows 上更趁手
5.1 postgresql.conf 开发机配置模板
安装完、能连接,这只是开始。开发调试场景下,默认配置有两个问题:一是内存参数偏保守,二是日志信息不够用。我本地一套适合开发机的配置模板大概是这样的:
shared_buffers = 256MB work_mem = 8MB maintenance_work_mem = 128MB max_wal_size = 1GB min_wal_size = 80MB log_min_duration_statement = 1000 log_line_prefix = '%t [%p]: user=%u,db=%d '参数含义和选择理由:
shared_buffers:PostgreSQL 共享缓冲区大小。开发机上不要贪大,8GB 内存的机器设 256MB 足够,设太高会和操作系统文件缓存争夺内存,得不偿失。这个参数修改后需要重启实例。work_mem:排序、哈希等操作的内存上限。设 8MB 是小额提升,不易触发内存溢出,又比默认的 4MB 对复杂查询友好一点。maintenance_work_mem:VACUUM、CREATE INDEX 时使用的内存。调大可以明显加快索引维护和批量操作,但也不能无止境,128MB 对开发库绰绰有余。max_wal_size和min_wal_size:9.6 已经用这两个参数替代了老式 checkpoint 配置。开发机没必要追求极致的点检频率,给 1GB 上限就行。log_min_duration_statement = 1000:把执行超过 1 秒的 SQL 全部记录下来。调试慢查询时这简直是救命稻草,默认值 -1 是什么都不记,建议开发环境一定改掉。log_line_prefix:给日志加上时间和用户名、数据库名,否则查慢 SQL 的时候都分不清是哪条连接产生的。
改配置之前,先copy D:\pgdata\9.6\postgresql.conf D:\pgdata\9.6\postgresql.conf.bak备份一份,这个习惯能让你在改错参数后一分钟内回到可用状态。
5.2 pg_ctl 手动启停:绕过服务管理器的方法
Windows 服务管理器操作起来没什么问题,但遇到需要指定参数启动、或者服务界面不配合的时候,pg_ctl是更顺手的工具。
手动启动:
pg_ctl -D D:\pgdata\9.6 start -l D:\pgdata\9.6\pg_log\manual.log手动停止:
pg_ctl -D D:\pgdata\9.6 stop -m fast这里要注意三个具体问题:
第一,pg_ctl start会用当前 Windows 用户身份启动 postmaster。如果你的当前用户对数据目录没有权限,启动会直接失败或卡住。Windows 下比较稳的做法是管理员权限的 cmd 里执行,或者先确认数据目录 ACL 里包含了当前用户。
第二,千万不要在服务管理器已经启动了实例的情况下,再去执行pg_ctl start。会报错提示另一个 postmaster 已经在运行,让你误以为系统出了问题,实际只是启动方式混用了。一次会话里选一种方式,要么一直用服务管理器,要么一直用命令行。
第三,-m fast是快速关闭,它会回滚未提交事务并断开连接,这是日常维护推荐的模式。不要轻易用-m immediate,那是崩溃恢复场景才用的,可能触发 WAL 恢复流程。
为什么要掌握手动启停?服务起不来的时候,正好可以用pg_ctl在前台直接带日志输出启动,能更快看到真实报错。另外,数据目录里的postmaster.opts文件记录了上次启动参数,手动排查时看一眼总没错。
5.3 日常备份、恢复与体检命令
9.6.10 的维护命令集,和后面几个版本差别不大,但务必用同版本目录下的工具,不要拿新版本的 pg_dump 去备份旧库再拿到新环境恢复,前后版本兼容性虽然大体在进步,但 9.6 到 16 的跨度足够产生意外。
备份用 pg_dump:
D:\PostgreSQL\9.6\bin\pg_dump -h 127.0.0.1 -U postgres -F c -f app_backup.dmp appdb恢复用 pg_restore,--clean表示先删除目标库中的同名对象再重建:
D:\PostgreSQL\9.6\bin\pg_restore -h 127.0.0.1 -U postgres -d appdb --clean app_backup.dmp日常体检,最常干的一件事是看表膨胀情况:
SELECT relname, n_live_tup, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 20;如果发现某张表死元组数量居高不下,执行:
VACUUM (VERBOSE, ANALYZE) your_table;9.6 的 VACUUM 语法和后面版本没太大变化,掌握这一套就足够应付开发库的日常维护了。Windows 上还能额外做一件事:定期给数据目录所在分区做快照或简单拷贝备份,数据目录独立放在 D 盘的好处到这里就完全体现出来了。
最后分享一点自己的体会。装完 9.6.10 之后,我做的第一件事永远是确认日志和 locale 两个点,而不是急着导数据。Windows 上调数据库,九成问题都集中在“服务跑没跑、监听有没有、pg_hba 让不让连、日志里怎么说”这四件事上,把这四件事串一遍,比在网上盲目搜报错快得多。如果你本地还要同时跑多个 PostgreSQL 版本,建议端口、数据目录分开管,写一个 start.bat/stop.bat 固定命令,把pg_ctl -D D:\pgdata\9.6 start -l ...这类指令固化下来,后面维护能省下一大堆重复操作。