安装与配置
本章解决一件事:把 PostgreSQL 18 干净地装到 Windows、Linux、macOS 或 Docker 上,然后摸清它的三个配置文件——数据放在哪、参数在哪改、谁能连进来。
PostgreSQL 是什么,什么时候选它
PostgreSQL 源自 1980 年代末伯克利的 Postgres 项目,三十多年持续开发,是目前演进最快的开源关系数据库。它是"对象-关系"型的:除标准 SQL 外,还有自定义类型、函数、操作符和扩展机制,第三方代码可以插进它的存储层和查询层。
跟 MySQL 做选型时,几个差异值得摆在桌面上看:
- 类型系统。PG 原生支持数组、范围、复合类型、枚举、域类型、
inet/cidr/macaddr、uuid等。“这列要存一组标签”、“这列存一段时间区间”,在 PG 里是一个列类型的事;在 MySQL 里往往退化成 JSON 或逗号分隔字符串。 - JSONB + GIN 索引。JSONB 是二进制存储的 JSON,可以建索引、可以和普通列写进同一个查询。半结构化数据不用另开一套存储。
- 扩展生态。PostGIS(地理)、pgvector(向量检索)、TimescaleDB(时序)、pg_cron(定时任务)……一台 PG 常常能干好几个专用数据库的活。MySQL 在这一维度的可选项少得多。
- 复杂查询。窗口函数、CTE、LATERAL JOIN、并行查询这些能力 PG 给得更完整也更早;报表和数据分析类负载在 PG 上通常写起来更顺手。
也要公平地说 MySQL 的好处:主从复制运维模型简单、部署足迹小、教程和 DBA 人才池更大、gh-ost 这类在线改表工具生态成熟。简单的主键驱动的 CRUD 业务,两者跑起来没有体感差别。
一句话决策:查询复杂、类型讲究、想把地理/向量/JSON 都收进一个库,选 PG;团队 MySQL 经验深厚、负载是典型的读多写少 Web 应用,MySQL 完全够。
版本现状(2026-10-20 实测):当前大版本是 18(补丁版 18.6),上一分支 17 已到 17.11。PG 每年秋季发布一个大版本,小版本只修 bug 和安全问题——这个节奏值得记进运维计划。本手册以 PG 18 为基线,兼顾 14–17。
PG 18 值得在安装当天就知道的三件事:异步 I/O 子系统(顺序扫描、位图扫描、vacuum 明显受益,见 01-架构与核心概念.md);initdb 默认开启数据页校验和(旧版本默认关,这是 18 的重要变化,--no-data-checksums 可关);MD5 口令认证被官方弃用,给角色设 MD5 口令会告警(下文 pg_hba 一节展开)。
各平台安装
Windows — EDB 图形安装器
官方 Windows 安装器由 EnterpriseDB 维护,从 postgresql.org 的 Downloads 页跳转即可。安装中真正要做决定的就几处:
- 安装目录默认
C:\Program Files\PostgreSQL\18,一般不动。 - 数据目录默认
C:\Program Files\PostgreSQL\18\data。机械盘 + SSD 双盘的机器,把数据目录放 SSD;数据库对 WAL 的 fsync 延迟极其敏感,别放网络盘或同步盘。 - 超级用户口令:属于
postgres角色(约定俗成的 root,装完第一步就该记住它)。 - 端口默认 5432,装了多个大版本会冲突,按提示换。
- 勾选安装 pgAdmin 4 和 Command Line Tools。
- 装完后会注册一个 Windows 服务
postgresql-x64-18,用services.msc或net stop/start postgresql-x64-18控制启停——Windows 上 PG 不是前台进程,改了配置要重启服务才生效。 - 把
C:\Program Files\PostgreSQL\18\bin加入 PATH(psql、pg_dump、pg_ctl 都在这),然后验证:
psql --versionpgAdmin 还是 psql? pgAdmin 适合浏览对象树、看会话、偶尔手敲 SQL;日常开发(迁移脚本、EXPLAIN 分析、批量导数)psql 更可控,而且本手册所有示例都可以直接粘进 psql 跑。建议以 psql 为主,pgAdmin 当仪表盘。
Linux — PGDG 官方源(推荐)
发行版自带的源里 PG 版本通常老两代(比如 Ubuntu 自带的还是 16)。生产环境请直接用 PGDG 官方 apt/yum 源。
Debian / Ubuntu:
# 一条脚本搞定:装 postgresql-common、取签名密钥、写源
sudo apt install -y postgresql-common ca-certs curl
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh # 交互确认即可
sudo apt update
sudo apt install -y postgresql-18
sudo systemctl status postgresql # Debian 打包会自动初始化集群并拉起服务手工配源的话:密钥存到 /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc,再写 /etc/apt/sources.list.d/pgdg.sources,注意发行代号要带 -pgdg 后缀(例如 trixie-pgdg),这是新格式最容易踩错的地方。apt.postgresql.org 访问慢的国内网络,可以换成清华镜像站(mirrors.tuna.tsinghua.edu.cn/postgresql/repos/apt/)——把源文件里的域名换掉即可,其余不变。
RHEL / Rocky / AlmaLinux:
# EL-9 替换成你实际的 major 版本(EL-8/9/10 均官方支持)
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql # RHEL 8+ 自带 module 会抢包,必须先禁用
sudo dnf install -y postgresql18-server postgresql18
sudo /usr/pgsql-18/bin/postgresql-18-setup initdb # 初始化数据目录
sudo systemctl enable --now postgresql-18RHEL 系的布局:二进制在 /usr/pgsql-18/bin,数据目录在 /var/lib/pgsql/18/data,服务名 postgresql-18。
装完默认只有本机 Unix socket 可连;远程访问要动 listen_addresses 和 pg_hba.conf,见下面两节。
macOS — Homebrew
brew install postgresql@18
brew services start postgresql@18 # 注册成 LaunchAgent,开机自启
psql --versionHomebrew 的公式默认不链接进 PATH,按安装输出的提示把 /opt/homebrew/opt/postgresql@18/bin(Intel 机器是 /usr/local/opt/...)补进 PATH。另外注意:brew 版把超级用户设成你的登录用户名而不是 postgres,且 Unix socket 上口令为空——它把 trust/peer 式开发便利做到了极致,这也是为什么生产永远跑在 Linux 上。
Docker — 最快起一个实例
docker run -d --name pg18 \
-e POSTGRES_PASSWORD=devpass123 \
-e POSTGRES_USER=app \
-e POSTGRES_DB=appdb \
-p 5432:5432 \
-v pg18-data:/var/lib/postgresql/data \
-v ./init:/docker-entrypoint-initdb.d:ro \
postgres:18几个必须明白的点:
POSTGRES_PASSWORD是硬性要求:没给口令又没显式设POSTGRES_HOST_AUTH_METHOD=trust,容器直接拒绝启动。这个设计就是为了防手滑。- 卷挂载位置警告:数据必须在
/var/lib/postgresql/data挂到命名卷或本地磁盘的 bind mount 上。不要让数据落在容器可写层——那是 overlayfs,大数据库的随机写会疯狂触发 copy-up,写放大严重,而且容器一删数据就没了。Linux 上用 bind mount 时记得宿主机目录要让容器里的 postgres 用户可写(uid 999),命名卷则没这个烦恼。 /docker-entrypoint-initdb.d:空数据卷首次初始化时,这个目录里的.sql/.sql.gz/.sh会按文件名顺序执行,适合建表种子数据。关键陷阱——只在数据目录为空的那一次执行,改了脚本重启容器不会重跑,先docker volume rm再启动。- 镜像默认把
listen_addresses设成*,并往 pg_hba 追加host all all all scram-sha-256(认证方法取自password_encryption默认值):只要 5432 映射出去,任何 IP 都能拿口令来试。跑在公网机器上时这等于面向互联网开门,要么收进内网/防火墙,要么改 pg_hba 白名单。
一份可以直接抄进项目的 compose:
services:
db:
image: postgres:18
container_name: pg18
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: devpass123
POSTGRES_DB: appdb
ports:
- "5432:5432"
volumes:
- pg18-data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d:ro
command:
- postgres
- -c
- shared_buffers=256MB
- -c
- log_min_duration_statement=500ms
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 5s
timeout: 5s
retries: 10
volumes:
pg18-data:用 command 传 -c 覆盖参数,或者往 /var/lib/postgresql/conf/conf.d 这类 drop-in 目录写 conf 片段,都不用动主配置文件。
数据目录与三个配置文件
数据目录(下文 PGDATA)启动后大致长这样:
PGDATA/
├── base/ # 每个数据库一个子目录(按数据库 OID 命名),普通表的数据文件都在这里面
├── global/ # 跨库共享的系统表:pg_database、pg_authid 等
├── pg_wal/ # WAL 日志,16MB 一个段文件——崩溃恢复的命脉,见 `01-架构与核心概念.md`
├── pg_xact/ # 全局事务提交状态,元组可见性要靠它判定(见 `06-MVCC与Vacuum.md`)
├── pg_stat/ # 统计信息,重启不丢;pg_stat_tmp/ 是运行期临时统计
├── PG_VERSION # 版本标记,缺了它 initdb 不认这个目录
├── postgresql.conf # 主配置
├── postgresql.auto.conf # ALTER SYSTEM 的落盘处,优先级高于 postgresql.conf
├── pg_hba.conf # 客户端认证:谁能从哪用什么方式连
└── pg_ident.conf # 用户名映射表,配合 peer/ident 认证用,绝大多数项目用不上三个文件的分工可以类比一栋楼:postgresql.conf 是整栋楼的电器参数(多大功率、开几部电梯);pg_hba.conf 是大门禁(谁能进门、查什么证件),只管进门,不管进门之后你能碰哪些房间——库内权限模型在 12-安全与权限管理.md;pg_ident.conf 是门禁系统里的"工牌名字对花名册"映射表。
各发行版把文件放的位置不一样(Debian 把 conf 挪到 /etc/postgresql/18/main/,RHEL 在 PGDATA 里,Windows 在数据目录下)。别记路径,用查询定位,永远可靠:
SHOW config_file;
SHOW hba_file;
SHOW data_directory; -- 需要超级用户改参数是 reload 还是 restart? 规则很简单:每个参数在文档和 pg_settings 里有一个 context 属性。postmaster 上下文必须重启;sighup 上下文 reload 即可;superuser/user 上下文甚至可以会话内 SET。查一下就知道,不用猜:
SELECT name, setting, context, sourcefile
FROM pg_settings
WHERE name IN ('shared_buffers', 'wal_level', 'work_mem');reload 不会中断现有连接,只影响新连接(会话级参数对已在跑的会话无效):
psql -c "SELECT pg_reload_conf();" # 或 systemctl reload postgresql-18 / pg_ctl -D $PGDATA reload核心参数:先讲为什么
下表默认值均为 PG 18 文档实测摘录。建议值假设一台 16GB 内存、SSD、专用数据库服务器。
# 参数 默认值 作用 生效方式
# listen_addresses localhost 监听哪些网卡 restart
# port 5432 TCP 端口 restart
# max_connections 100 最大并发连接数 restart
# shared_buffers 128MB 数据库自己的页缓存 restart
# effective_cache_size 4GB 优化器对总可用缓存的估计 reload
# work_mem 4MB 单节点排序/哈希内存上限 reload
# maintenance_work_mem 64MB vacuum/建索引/恢复的内存 reload
# wal_level replica WAL 记录详细程度 restart
# checkpoint_timeout 5min 自动 checkpoint 最大间隔 reload
# max_wal_size 1GB 两个 checkpoint 间 WAL 软上限 reload
# wal_compression off 压缩 WAL 里的整页镜像 reload
# random_page_cost 4.0 优化器估计的随机页读成本 reload
# log_min_duration_statement -1 慢于多少毫秒的语句记日志 reload逐个说清楚为什么。
listen_addresses。默认只监听 127.0.0.1,这是新手"明明装了却连不上"的第一现场。改成 '*' 只是开了门,真正决定谁能进来的是 pg_hba——两者永远配套修改,也永远不要配套改成"全开 + trust"。改这个要 restart,因为监听套接字是 postmaster 启动时建的。
max_connections。PG 是每连接一个进程(不是线程,见 01-架构与核心概念.md)。一个 backend 进程有自己的内存上下文和内核栈,空闲状态就占几 MB,查询起来再叠加 work_mem 用量;500 个连接的基线开销就是几个 GB,还要算上调度和锁的压力。所以生产的正确姿势不是把它调到 2000,而是让它保持 100–300 这个量级,应用侧用连接池(pgbouncer 或驱动自带池)把成百上千的空闲连接收敛成几十个真实连接。一个粗略公式:连接池上限 × 应用实例数 ≈ max_connections 的 80%,留出运维通道。超了再调池子,不是调数据库。
shared_buffers。PG 自己管理的页缓存,默认 128MB 小得可笑——那是安全出厂值,不是性能值。官方建议的起点是内存的 25%,而不是"越大越好",原因是 PG 不做 O_DIRECT,脏页和未命中读取都依赖操作系统 page cache 这第二层缓存(PG 默认走内核的 buffered I/O,自己负责 fsync)。shared_buffers 给到 50% 只会挤压 OS cache,同一份热数据在两层里各存一遍,纯浪费。文档原话:超过内存 40% 很难比更小的值更好。调大它的同时记得调大 max_wal_size——更多脏页意味着 checkpoint 之间要攒更多 WAL。
effective_cache_size。名字骗人:它不分配任何内存,只是告诉优化器"这台机器上数据大概率在缓存里",影响的是索引扫描 vs 顺序扫描的选择。设成 shared_buffers + 预计 OS 可用缓存之和,实践中 16GB 机器给 12GB 左右。它是 reload 生效,调错了随时改回来。
work_mem。最容易出事故的一个参数。它是每个查询节点、每个排序/哈希操作的独立预算——一个带多个 join 和排序的查询里,hash join 的内存上限还要再乘 hash_mem_multiplier(默认 2.0),并且所有并发连接同时各自消耗。“work_mem 设 256MB"的真实含义可能是 256MB × 节点数 × 连接数,机器不爆才怪。默认 4MB 对 OLTP 是合理的;报表用户单独 SET work_mem='128MB' 或用角色级默认值,比全局调大安全得多。乘号陷阱的量化展开与排序溢盘排查见 09-查询优化与EXPLAIN.md。
maintenance_work_mem。vacuum、CREATE INDEX、恢复 pg_dump 这类维护操作专用,跟并发无关(一个会话一次只跑一个维护操作),可以放心给几百 MB—— restores 会明显变快。唯一注意:每个 autovacuum worker 也会按这个值分配,想单独控制就用 autovacuum_work_mem。
wal_level / checkpoint_timeout / max_wal_size。wal_level 三档 minimal / replica / logical:minimal 只记崩溃恢复所需的信息,代价是开不了归档也做不了物理复制(archive_mode 与 minimal 互斥,max_wal_senders 不为 0 时服务器直接拒绝以 minimal 启动);默认 replica 适合绝大多数部署;只有逻辑复制/CDC 才上 logical,它多记的开销是持续的。checkpoint 每 checkpoint_timeout(默认 5 分钟)或 WAL 攒到 max_wal_size(默认 1GB,注意是软上限)触发一次,作用与"刷脏风暴"见 01-架构与核心概念.md;批量导入期间把 max_wal_size 临时调大能压掉一波波的检查点延迟尖刺。
wal_compression。默认 off。开启后压缩 WAL 里的整页镜像(主要来源是 full page image——checkpoint 后首次修改的页要整页写 WAL,量不小),省的是存储和复制带宽,换的是 CPU。方法可选 pglz(on 是它的历史拼法)、lz4、zstd(后两者取决于编译选项,发行版包通常都带)。写带宽紧、CPU 有余量的实例值得一开,PG 14+ 的 lz4/zstd 开销已明显低于 pglz。
random_page_cost。默认 4.0 诞生于机械盘时代——随机读一页约等于 4 倍顺序读。SSD 的随机读延迟和顺序读只差几倍以内,所以 SSD 机器上普遍调到 1.1–1.2:不改的话,优化器会系统性高估索引扫描的代价,明明该走索引却选了顺序扫描,这是"明明建了索引却不走"的经典元凶之一。它同样不分配任何东西,纯粹是成本模型的输入。
log_min_duration_statement。默认 -1(关闭)。开发环境设 250ms、生产从 1s 起步逐步收紧,慢查询日志是发现坏 SQL 最便宜的雷达。每条日志都要写盘,设成 0(打印全部)本身就会拖慢高吞吐实例。配合 log_checkpoints(默认已开)和 log_line_prefix 一起规划,运维细节见 11-备份恢复与运维.md。
pg_hba.conf:谁能进门
规则一行一条,字段依次是 TYPE / DATABASE / USER / ADDRESS / METHOD(ADDRESS 列在 TYPE 为 local 时不存在,Unix socket 没有 IP 概念)。匹配逻辑必须记住两条:自上而下,第一条命中即生效;一条都不命中则默认拒绝。排错时先怀疑顺序——把宽规则写在窄规则前面,后面的窄规则就是死代码。
psql -c "SELECT line_number, type, database, user_name, address, auth_method FROM pg_hba_file_rules;" # 看解析结果,error 列非空说明有语法错
psql -c "SELECT pg_reload_conf();" # 改完 reload 即可生效,不用重启常用 METHOD 一句话版:
scram-sha-256:当前推荐。挑战-应答式,口令本身从不过网(服务器只见哈希),PG 14 起password_encryption默认就是它。md5:正在被淘汰。除算法强度外,md5 认证会把口令的等价物送过网络;PG 18 起官方已明确弃用(给角色设 md5 口令就告警,未来大版本会移除),存量实例尽快迁到 scram。trust:来了就让进,不要口令。只配出现在"本机开发机"语境里,任何能被外部访问的实例上用 trust 都等同裸奔。peer:Unix socket 上比对操作系统用户名。Linux 上sudo -u postgres psql免密,就是默认 pg_hba 里的 peer 在工作。ident:向对端主机索要真实 OS 用户名做校验,跨网络信任边界模糊,用得越来越少。reject:显式拒绝某段地址/某批用户,写黑名单时有用。
Windows 上没有 Unix socket,local 行不起作用,所有连接都走 host。
开发机最小可用(图省事,仅限本机回环):
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256生产最小可用(本机运维 + 应用网段 + 复制专用):
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
host all all 127.0.0.1/32 scram-sha-256
host appdb app 10.20.0.0/16 scram-sha-256
host replication repl 10.20.0.5/32 scram-sha-256
host all all 0.0.0.0/0 reject注意生产示例里应用账号 app 只允许从应用网段访问 appdb,而不是"所有库所有网段”;最后一行 reject 是显式兜底(不写也默认拒绝,但写出来意图清楚)。TLS、pgbouncer 接入后的地址写法、以及进门之后的权限模型,见 12-安全与权限管理.md。
psql 速查
连接串两种写法等价:
psql "postgres://app:devpass123@db.internal:5432/appdb?sslmode=require&application_name=cli"
psql -h db.internal -p 5432 -U app -d appdb -w-w 禁止回退到终端输口令(脚本里防卡死),sslmode=require 在生产默认带上。PGPASSWORD 环境变量或 ~/.pgpass 文件可以避免口令进命令行,后者权限必须 0600。
元命令(在 psql 交互里敲):
\l列数据库,\c appdb切库,\conninfo确认当前连到哪\dt/\dt+列表(带+显示大小、描述),\dn列 schema,\dx列扩展\d orders看表结构 + 索引;\d+ orders再多显示存储、TOAST 信息和表占用\x打开扩展显示——一行字段多时竖排输出,读pg_stat_activity救命用\timing打开每条语句的耗时-E连同元命令展开出的内部 SQL 一起打印(想知道\d到底查了什么系统表,就靠它)
非交互执行,脚本化必备:
psql -h 127.0.0.1 -U app -d appdb -c "SELECT count(*) FROM orders;" # 一条语句,跑完退出
psql -v ON_ERROR_STOP=1 -f ./migrations/001_init.sql # 跑文件,出错立即退出而不是继续
psql -d appdb -c "COPY orders TO STDOUT WITH CSV HEADER" > orders.csv确认版本和数据目录位置:
SELECT version(); -- PostgreSQL 18.6 on x86_64... 带平台和构建信息
SHOW server_version; -- 只要版本号
SHOW data_directory; -- 需要超级用户装完先跑一遍验证清单
# 1. 能连上、版本对
psql -h 127.0.0.1 -U postgres -c "SELECT version();"
# 2. 关键配置看一眼
psql -c "SHOW listen_addresses;" -c "SHOW data_directory;" \
-c "SELECT name, setting FROM pg_settings WHERE name IN ('password_encryption','wal_level','shared_buffers');"
# 3. 18 默认应有数据校验和(initdb 默认开启)——校验和是集群属性,用 pg_controldata 直接看
pg_controldata "$PGDATA" | grep -i checksum # 期望 "Data page checksum version: 1"
# 4. 真写一笔:建表、插入、回读、删掉,证明 WAL 和 fsync 链路是通的
psql -c "CREATE TABLE t_probe(id int); INSERT INTO t_probe VALUES (1); SELECT * FROM t_probe; DROP TABLE t_probe;"第 4 步不是形式主义:容器里 volume 挂错、权限不对时,前面都正常,一写就炸。
版本升级路径概述。小版本(18.1 → 18.6)只换二进制、重启服务即可,数据格式不变,理论上无需备份但升级前照例先 pg_dump 或物理备份一份。大版本(17 → 18)两条主流路线:一是 pg_upgrade 原地迁移,快(--link 模式近乎瞬时,但迁移后旧目录就不能回退使用了;18 要求新旧集群的校验和配置一致,这正好是上面 checksum 那条命令的用处),适合停机窗口能接受的场景;二是逻辑复制,旧版本当发布端、新版本当订阅端,数据追平后切流,停机接近零、还能顺便把新实例当演练环境,代价是搭建复杂。两条路的完整操作在 10-复制与高可用.md 和 11-备份恢复与运维.md。
实战建议
- 生产环境一律 PGDG 源装,别用发行版自带包——大版本和补丁都会落后,等到出事才发现修 CVE 的版本装不上。
- Docker 只用于本地开发和临时验证;上生产优先裸进程或 systemd,至少把数据卷放在专用本地盘上,别把 overlayfs 当存储驱动。
- 改任何参数前,先
SELECT name, context FROM pg_settings WHERE name = '参数名';——知道是 reload 还是 restart,能避免一次不必要的重启事故。 - 连接数焦虑先动 pgbouncer,再动 max_connections。看到"把 max_connections 调到 5000"的帖子,基本可以判断作者没在生产环境看过 PG 的内存分配。
- pg_hba 每次改动后立刻用
pg_hba_file_rules验证解析结果,再 reload;行内注释(#)紧跟方法后面是合法的,但整行注释别写成多行粘连。 - 18 的新实例默认有校验和、口令默认 scram,这两件事不用再手动配置;从 ≤13 一路升级上来的老库则务必确认这两项,老习惯是事故的温床。