Skip to content
常见陷阱与最佳实践

常见陷阱与最佳实践

全手册陷阱汇总 + 高危坑深讲 + 上线自检清单。基线 PostgreSQL 18(2026-10 实测最新补丁版 18.6,兼顾 14–17);每条陷阱后标注展开章节,这里只给"现象 → 后果 → 一招制敌"。


一、陷阱汇总表

安装与配置

陷阱 后果 对策 详见
listen_addresses 忘改(默认 localhost) 远程连接死活不通,还以为是防火墙 改成 * 或指定地址后 reload,再配 pg_hba 00-安装与配置.md
Docker 数据目录挂到宿主机容器层(忘了 -v) 容器一删库没;放 overlayfs 上写性能也差 命名卷或独立盘挂载 /var/lib/postgresql/data 00-安装与配置.md
改 postgresql.conf 不 reload/restart 参数看似改了实际没生效 SELECT pg_reload_conf();;分不清哪些要重启就查 pg_settings.context 00-安装与配置.md
shared_buffers 拉到内存 80% OS page cache 被挤饿,整体更慢还加剧 OOM 风险 25% 左右起步,够不着别硬凑 00-安装与配置.md
max_connections 盲目调大 每连接一个进程,几千连接把机器压成筛子 应用侧收口 + PgBouncer,而不是抬上限 01-架构与核心概念.md
pg_hba 把宽松规则放前面 首条命中制,后面的严格规则全成摆设 严格在前宽松在后,改完用 pg_hba_file_rules 视图核对 00-安装与配置.md
用 trust 图省事 本机任意进程免密登录数据库 开发容器也别用,一律 scram-sha-256 12-安全与权限管理.md

类型与 DDL

陷阱 后果 对策 详见
timestamp 不带时区 跨国/夏令时/应用与库时区不一致,时间线集体漂移 军规:一律 timestamptz 02-数据类型与DDL.md
随机 UUID v4 做大表主键 B-tree 写入打散全树、缓存命中率崩、索引膨胀 时序场景改 uuidv7()(PG 18)或 BIGSERIAL/IDENTITY 02-数据类型与DDL.md
用 serial 忘了它是旧惯用法 sequence 与列的归属/权限行为差异在迁移时咬人 新表一律 GENERATED ... AS IDENTITY 02-数据类型与DDL.md
float 存钱 0.1+0.2 的经典误差进账单 numeric(12,2) 02-数据类型与DDL.md
char(n) 存变长文本 尾部补空格,比较和展示全是惊喜 text(需要约束再 varchar(n)) 02-数据类型与DDL.md
亿行大表直接 ADD COLUMN ... NOT NULL / ALTER COLUMN type 全表重写 + ACCESS EXCLUSIVE 长锁,站点瘫痪 先 nullable 再回填;NOT NULL 用 CHECK 证明配方(PG 12 起);大改走逻辑复制迁移 02-数据类型与DDL.md
DROP TABLE ... CASCADE 顺手一敲 依赖它的视图、外键全被连坐删除 先不带 CASCADE 看报错清单再决定 02-数据类型与DDL.md
enum 加值 + prepared statement 缓存 老会话用新值报错,重启才好的玄学 低频变更可接受;高频状态列干脆用 text + CHECK 02-数据类型与DDL.md
对生成列直接 UPDATE 报错 0…generated column 改它依赖的源列 02-数据类型与DDL.md

查询写法

陷阱 后果 对策 详见
LEFT JOIN 右表过滤条件写进 WHERE 静默退化成 INNER JOIN,“丢了的数据去哪了"查三小时 右表条件放 ON,判丢失放 WHERE 03-SQL查询实战.md
NOT IN (子查询) 且子查询可能出 NULL 整条查询返回空集(三值逻辑) 一律改 NOT EXISTS 03-SQL查询实战.md
BETWEEN 配时间戳上界 边界秒/毫秒漏行或多行 >= AND < 左闭右开 03-SQL查询实战.md
对列套函数:WHERE date_trunc(...)/::date 索引报废全表扫 改写为列裸露的区间谓词 04-索引实战.md
OFFSET 深分页 offset=1000000 仍要扫过再丢弃,越翻越慢 keyset(行比较 + ORDER BY 带唯一 tie-breaker) 03-SQL查询实战.md
DISTINCT ON 的 ORDER BY 与目标不一致 每组取到的"第一行"不符合预期 DISTINCT ON 列必须是 ORDER BY 前缀 03-SQL查询实战.md
字符串 || 遇 NULL 整串变 NULL CONCAT() 自动忽略 NULL 03-SQL查询实战.md
数据修改型 CTE 里"改完再读同一张表” 语句级快照看不到 CTE 的改动;副作用顺序不保证 一律从 RETURNING 取数 03-SQL查询实战.md
ON CONFLICT 不带冲突目标 / 猜约束名 语法报错;部分索引上的唯一约束要带 WHERE 明确 (列) 或 ON CONSTRAINT 名 03-SQL查询实战.md

索引

陷阱 后果 对策 详见
低基数列(bool/枚举)单列索引 优化器压根不走,白付维护费 组合进复合索引或用部分索引 04-索引实战.md
软删除表所有索引不带 WHERE deleted_at IS NULL “已删除"行永远占索引,越跑越肥 部分索引只索引活行 04-索引实战.md
CREATE INDEX CONCURRENTLY 失败就完事 留下 INVALID 索引占空间还拖累写 查 pg_index.indinvalid,DROP 后重建 04-索引实战.md
SSD 不降 random_page_cost(默认 4.0) 优化器嫌弃索引扫、偏爱 seq scan SSD 集群调到 1.1–1.2 09-查询优化与EXPLAIN.md
LIKE ‘%xx%’ 想靠 B-tree 前导通配无解 pg_trgm + GIN;自然语言上全文检索 04-索引实战.md
BRIN 建在会被随机更新的列 块摘要持续加宽,索引失效 BRIN 只配"只追加不重排"的时序大表 04-索引实战.md
FK 引用列不建索引 级联删改/反查全表扫 引用列显式建 B-tree 04-索引实战.md
迷信 index-only scan 的 INCLUDE vacuum 跟不上时 VM 位没置,照样回表 先治 autovacuum 再谈覆盖收益 06-MVCC与Vacuum.md

事务与锁

陷阱 后果 对策 详见
DDL 不带 lock_timeout ALTER 排队拿 ACCESS EXCLUSIVE,把后面所有 SELECT 堵成全站事故 SET lock_timeout='5s' + 重试窗口执行 05-事务与锁.md
以为 REPEATABLE READ 能防写偏斜 PG 的 RR 只是快照隔离,双窗口/预约类业务照样穿 关键冲突点 SELECT ... FOR UPDATE,或上 SERIALIZABLE 05-事务与锁.md
上 SERIALIZABLE 却不处理 40001 偶发"序列化失败"报错被当数据库坏了 捕获 40001/40P01 整事务重试 05-事务与锁.md
任务队列用状态 CAS 逐行抢占 热点页高并发空转;FOR UPDATE 不带 SKIP LOCKED 则 worker 排队 领取一律 FOR UPDATE SKIP LOCKED 05-事务与锁.md
多行更新不加统一排序 两个事务以不同顺序拿行锁 → 死锁 ORDER BY id 固定加锁顺序 05-事务与锁.md
idle in transaction 挂满连接池 连接占着、事务 pin 着 xmin,vacuum 和池双输 idle_in_transaction_session_timeout + 代码审查 05-事务与锁.md
synchronous_commit=off 当成"性能开关” 崩溃丢最近最多若干提交 只在可容忍小窗口的场景开 05-事务与锁.md
大事务一次 UPDATE 亿行 锁持有久、膨胀风暴、失败全回滚 分批 + 每批独立事务 06-MVCC与Vacuum.md

MVCC 与 Vacuum

陷阱 后果 对策 详见
关 autovacuum “怕它抢 IO” 死元组堆积、索引肥大、最后 xid 回绕被强制停机清理 永远开着,按表调参 06-MVCC与Vacuum.md
亿行表默认 autovacuum_vacuum_scale_factor=0.2 改 20% 才动手 = 两千万死元组才清理 大表按表调小(0.01–0.05)+ 调小 threshold 06-MVCC与Vacuum.md
把 VACUUM FULL 当日常 重写整表 + ACCESS EXCLUSIVE 锁,大表等于停站 日常 VACUUM 即可;真要瘦身上 REINDEX CONCURRENTLY/分区重建 06-MVCC与Vacuum.md
不看事务 ID age 逼近回绕线时数据库拒写才慌 监控 age(datfrozenxid),告警线远早于官方红线 06-MVCC与Vacuum.md
复制槽遗留不管 主库 WAL 被无限保留,盘满宕机 max_slot_wal_keep_size 兜底 + 定期清 inactive 槽 10-复制与高可用.md

高级 SQL 与分区

陷阱 后果 对策 详见
窗口帧想当然按 ROWS 默认是 RANGE,并列行全算进累计——“对不上的合计” 显式写帧,懂 RANGE/ROWS/GROUPS 差异 07-高级SQL.md
递归 CTE 不防环 脏数据成环时无限递归直到报错/挂死 path 数组或 PG 14+ 的 CYCLE 子句 07-高级SQL.md
分区表想要"全局唯一约束" PG 唯一约束必须包含分区键,做不到 业务层去重或单表方案,接受现状 07-高级SQL.md
大 jsonb 对象高频局部更新 每次整值重写,写放大 + TOAST 膨胀双杀 核心字段拆列;jsonb 放稀疏只增不改的属性 07-高级SQL.md
中文全文检索指望开箱即用 内置 regconfig 不分中文词 zhparser/pg_jieba 等扩展,或上层 ES(见 elasticsearch 手册) 07-高级SQL.md

Go 客户端(pgx)

陷阱 后果 对策 详见
pgx 的 prepared 语句过 PgBouncer transaction 模式 could not open ... prepared statement 类报错 PgBouncer ≥1.21 开 max_prepared_statements,或降 session 模式 09-查询优化与EXPLAIN.md
循环里逐行 Exec 插十万行 网络往返打满,导入慢一个数量级 CopyFrom / SendBatch 08-Go客户端pgx.md
Query 不传带超时的 context 慢查询吊死请求 goroutine 每个调用都传 ctx 08-Go客户端pgx.md
pgxpool 默认 MaxConns=4 当终值 高并发下池排队成为隐形限流 按负载显式调,同时与 PG 端 max_connections 对齐 08-Go客户端pgx.md
numeric 列扫进 float64 钱进了浮点 pgtype.Numeric(或扫进 [16]byte/string 再转) 08-Go客户端pgx.md
抄 2023 年前的 pgx 教程 API 名早变了(v5.11 源码无 ForRows/GetStatuses 等) 以 pkg.go.dev 与源码为准 08-Go客户端pgx.md

复制与高可用

陷阱 后果 对策 详见
拿复制当备份 误删/勒索/坏 DDL 秒级同步到所有副本 备份+归档独立于复制,异地一份 11-备份恢复与运维.md
以为 async 复制 + synchronous_commit=on 就不丢数据 on 只保证到主库 WAL;主库盘坏仍可能丢 RPO=0 需要同步档 + quorum 设计 10-复制与高可用.md
不用槽也不用 wal_keep_size 备库掉线片刻,WAL 已被回收、复制断裂 至少 wal_keep_size 垫底,讲究就用槽 10-复制与高可用.md
逻辑复制当全量同步用 不搬 DDL、序列、大对象、截断 只当 DML 管道;序列切换时手工 setval 10-复制与高可用.md
读写分离不看一致性 刚写完读备库读到旧值,用户以为"没保存" 写后读走主库/等待 replay LSN 10-复制与高可用.md

备份恢复与运维

陷阱 后果 对策 详见
从没演练过恢复 备份文件躺在对象存储里"薛定谔可用" 定期全量恢复演练 + pg_restore --list 抽检 11-备份恢复与运维.md
只 pg_dump 不备 globals 新实例恢复后角色/授权全缺 pg_dumpall --globals-only 一并备 11-备份恢复与运维.md
建库时关了数据校验和 静默页腐化多年后爆雷 PG 18 默认已开;老集群补开要停机 11-备份恢复与运维.md
从不看 log_min_duration_statement(默认关) 慢查询事故无现场可查 生产设 250ms–1s,配 auto_explain 11-备份恢复与运维.md
K8s 里拿默认 StorageClass 跑 PG 网络盘抖动/丢失拖垮数据库 本地盘/高性能卷 + 备份进应用层,见 k8s 手册 11-备份恢复与运维.md

安全与权限

陷阱 后果 对策 详见
应用连库用超级用户 注入/误操作的爆炸半径 = 整个实例 应用角色 DML-only,DDL 迁移角色分离 12-安全与权限管理.md
新表忘了重新 GRANT “为什么应用查不到刚建的表” ALTER DEFAULT PRIVILEGES 预设 12-安全与权限管理.md
RLS 忘了 FORCE ROW LEVEL SECURITY 表 owner 绕过策略,隔离形同虚设 策略 + FORCE + 应用角色非 owner 12-安全与权限管理.md
动态标识符(表名/列名/排序方向)拼字符串 占位符替不了标识符,照样注入 应用层白名单 + format('%I'/'%L') 12-安全与权限管理.md
md5 口令不迁移 官方已弃用(18 起设置即告警),离线可破 password_encryption=scram-sha-256 + 重设口令 12-安全与权限管理.md
DSN 不写 sslmode=verify-full prefer 模式下中间人降级明文 生产显式 require/verify-full 12-安全与权限管理.md

服务端编程(函数与存储过程)

陷阱 后果 对策 详见
把包着 now() 的函数标成 IMMUTABLE 规划期被折叠成常量,表达式索引/生成列静默存旧值,改函数也不重算 波动性是对优化器的承诺:依赖时间标 STABLE,永远同值才 IMMUTABLE 13-函数与存储过程.md
SECURITY DEFINER 不固定 search_path 配合 CVE-2018-1058 式投毒,函数体里未限定的名字命中攻击者对象,属主权限变特洛伊木马 SET search_path='' + 函数体全 schema 限定 + EXECUTE 收紧 13-函数与存储过程.md
以为过程内 COMMIT 百无禁忌 事务控制只在顶级 CALL 可行:包在事务块里、DEFINER 过程、带 SET 子句的过程都直接报错 批量脚本让调用方不套事务;踩到限制就换回应用层分批 13-函数与存储过程.md
CREATE OR REPLACE 当万能替换 返回类型/OUT 列改不了;引用旧函数的视图、触发器要重建 把签名当 API 契约;要换就 drop 重建,进迁移脚本 13-函数与存储过程.md
WHEN OTHERS 一把吞 异常被静默吃掉,数据漏了且日志里查无此事 按具体条件名捕获;GET STACKED DIAGNOSTICS 记完再抛 13-函数与存储过程.md

触发器

陷阱 后果 对策 详见
百万行导入挂着行级触发器 每行一次函数调用的税,导入慢出倍数 导入期 DISABLE TRIGGER,或改写 AFTER STATEMENT + 过渡表批量化 14-触发器.md
以为 UPDATE OF 等于"值变了" SET 清单提及该列就触发,SET x = x 照样触发 判"真变了"用 OLD.col IS DISTINCT FROM NEW.col 14-触发器.md
同一事件多触发器靠创建顺序兜底 PG 的执行顺序是触发器名字母序,与创建顺序无关 trg_01_/trg_02_ 前缀显式控制 14-触发器.md
session_replication_role=replica 全程开着搬数据 外键检查连同触发器被一起跳过,事后"约束没违反/审计缺段"难查 只在完整性有外部保证时用,用完立刻还原 origin 14-触发器.md
以为事件触发器能授权给普通治理角色 只有超级用户可创建(18 文档现文) 治理类事件触发器由迁移/DBA 角色安装,定义进版本管理 14-触发器.md

扩展与生态

陷阱 后果 对策 详见
Timescaledb/pg_cron/pgAudit 没进 shared_preload_libraries CREATE EXTENSION 报错,或装上后后台任务静默不跑 ALTER SYSTEM 设 preload 并重启;改值时警惕覆盖掉原有列表 15-扩展与生态.md
大版本升级后不 ALTER EXTENSION UPDATE 二进制是新版本、库内对象还停在旧版本,行为漂移埋进暗处 升级清单里逐扩展过一遍 UPDATE 15-扩展与生态.md
DROP EXTENSION ... CASCADE 顺手一敲 依赖该扩展的视图、生成列连坐被删 先不带 CASCADE 看报错清单再决定 15-扩展与生态.md
untrusted 语言(plpython3u 等)随手装 函数可读写服务器文件系统、开网络连接,注入面从 SQL 挪进宿主语言 公司环境原则上不装;要 Python 生态先评估应用层方案 15-扩展与生态.md
扩展组合"全都要" 每个都是攻击面 + 升级债,出事故时排障面翻倍 准入三问(谁维护/要不要 preload/挂了能否绕过),装前走变更评审 15-扩展与生态.md

二、七个高危坑

1. 随机 UUID 主键:一张表自己把自己挤胖

uuid v4 毫无顺序,插入把 B-tree 全树搅花:每个叶子页都不满又都占缓存,缓存命中率随数据量线性下滑,索引体积明显大于同规模 BIGSERIAL。PG 18 的 uuidv7() 把时间戳放进 UUID 高位,写回落到右侧热点页,局部性与自增主键相当——需要 UUID 形态(多节点生成、对外暴露)就用 v7;纯内部主键,bigint GENERATED ALWAYS AS IDENTITY 仍是体积与速度的双重最优。存量 v4 大表不建议停机重排,新表开始止损。

2. OFFSET 深分页:翻页越深越接近全表扫

LIMIT 20 OFFSET 1000000 并不是"跳过"一百万行——执行器老老实实取出并丢弃它们,代价线性上涨,还伴随 MVCC 下更多死元组扫描。keyset 分页把"上一页最后一行"变成谓词:WHERE (created_at, id) < ($1, $2) ORDER BY created_at DESC, id DESC LIMIT 20,配 (created_at, id) 复合索引后每页恒定成本。下拉无限滚动、导出任务、队列消费全部适用。

3. 一条 DDL 堵死全站:ACCESS EXCLUSIVE 排队风暴

锁矩阵里只有一档能拦住普通 SELECT——ACCESS EXCLUSIVE。ALTER TABLE 这类语句先拿到锁再执行;执行期间的排队会级联阻塞其后所有语句:一条"看起来只改元数据"的 DDL,配上一次长时间回填,就是全站超时的事故报告。标准防线三件套:SET lock_timeout = '5s'(拿不到就放弃,不排队)、业务低峰执行、失败重试;真正的大改动走"nullable 新列 + 分批回填 + 改名切换"或逻辑复制迁移(见 10-复制与高可用.md)。

4. 隔离级别的两个"想当然"

想当然之一:开了 REPEATABLE READ 就高枕无忧——PG 的 RR 是快照隔离,两个并发事务各自"看到旧值、写不同行"的写偏斜它防不住(双医生值班/双窗口超卖,见 05-事务与锁.md)。想当然之二:上了 SERIALIZABLE 就一劳永逸——SSI 的代价是冲突时返回 40001 序列化失败,应用没有重试逻辑就等于把数据库正确性转嫁成用户可见报错。正确姿势:关键冲突要么 FOR UPDATE 折叠成单点,要么 SERIALIZABLE + 整事务指数退避重试。

5. 长事务与遗留槽:不动声色的双料炸弹

一个忘 COMMIT 的交互事务、一个没人消费的复制槽,都会把 xmin horizon 或 WAL 保留点钉在原地:前者让 autovacuum 全域失效(死元组越积越多、索引膨胀、最后逼近回绕强制清理),后者让 pg_wal 只增不减直到盘满。两者在监控上都是"安静的"——必须显式配告警:pg_stat_activity 中 xact_start 超阈值、pg_replication_slots 的 restart_lsn 落后水位、age(datfrozenxid) 曲线。防线参数:idle_in_transaction_session_timeout、max_slot_wal_keep_size。

6. 复制不是备份,没演练的备份不算备份

复制忠实同步一切,包括 DELETE、DROP、以及勒索 SQL;延迟删除(recovery_min_apply_delay)只能算缓解手段。真正的安全垫是:定时全量(pg_dump 或 basebackup)+ WAL 归档 + 可组合的 PITR,三者都放在复制够不着的地方(对象存储/异地)。并且每季度真的恢复一遍到临时实例——恢复路径从未走通的备份,等价于没有备份。

7. SECURITY DEFINER 不固定 search_path:库内提权机器

DEFINER 让函数体以属主的权限执行——设计意图是让小权限角色借用一次精确的高权限操作。但函数体的 SQL 在调用者的会话环境里解析:对象名沿调用会话的 search_path 查找,只要函数体里有不带 schema 限定的名字,而攻击者能控制自己会话的 search_path(或在可写 schema 里预埋同名对象——15 之前的 public 人人可建,正是 CVE-2018-1058 的土壤),高权限函数就会打在假对象上。官方给出的安全写法就三件事:定义时固定 SET search_path(文档示例 SET search_path = admin, pg_temp,pg_temp 垫底)、函数体内只写完全限定名、EXECUTE 精确授权。存量排查一行搞定:SELECT oid::regproc, provolatile, prosecdef FROM pg_proc WHERE prosecdef;——每个 DEFINER 都值得过一遍(写法模板见 13-函数与存储过程.md,投毒背景见 12-安全与权限管理.md)。


三、最佳实践清单

设计期

  • 时间一律 timestamptz;钱一律 numeric;文本一律 text。
  • 主键:内部表 BIGINT IDENTITY,多节点/对外用 uuidv7()(PG 18+)。
  • 软删除配部分索引;状态列配部分索引或组合索引,别为 bool 单开索引。
  • 每张表标配 created_at/updated_at(updated_at 用触发器或应用维护)。
  • FK 列显式建索引;级联删除慎设 CASCADE。

编码期

  • SQL 一律参数化;动态标识符走白名单 + format('%I')。
  • 事务保持最短;捕获 40001/40P01 并整事务重试;杜绝 idle in transaction。
  • 队列消费用 FOR UPDATE SKIP LOCKED;翻页用 keyset;导入用 COPY/Batch。
  • Go 端每个 db 调用传 context;numeric 不扫 float64;pgx 以当前版本文档为准。
  • RETURNING 取代"写完再查";批量写用 CTE/unnest 折成单语句。
  • 服务端逻辑按变更频率分工:常变的业务规则留在 Go,数据不变量(状态机守卫、updated_at、审计)落到触发器/约束;写的每个函数显式声明波动性、对象名带 schema。

运维期

  • autovacuum 永远开着;亿行级大表单独调参(scale_factor、threshold、cost)。
  • 监控四件套:磁盘(含 pg_wal 水位)、事务回绕 age、长事务/遗留槽、复制延迟。
  • 备份三件套:全量 + WAL 归档 + 定期恢复演练;globals 单独备。
  • 慢查询:log_min_duration_statement + pg_stat_statements + auto_explain 三管齐下。
  • 变更剧本化:DDL 带 lock_timeout;CONCURRENTLY 类操作验证 INVALID 残留。
  • 函数、触发器、事件触发器、扩展一律进版本化管理;热表触发器备一份过渡表批量版和"导入期禁用"预案;大版本升级清单含逐扩展 ALTER EXTENSION UPDATE。

安全期

  • 角色三分:应用(DML)/迁移(DDL)/报表(只读);生产 DSN 永不含超级用户。
  • ALTER DEFAULT PRIVILEGES 预设授权;新集群(15+)之外记得补收 public schema。
  • RLS 多租户三件套:策略 + FORCE + 应用角色非 owner。
  • scram-sha-256 起步,md5 存量尽快轮换;连接串 sslmode=verify-full。

四、上线自检清单

  • wal_level、max_connections、shared_buffers、work_mem、random_page_cost 按机型确认过(默认值是测试用的,不是生产用的)
  • pg_hba 逐条 review:无 trust、无宽段地址、无 md5 存量口令
  • log_min_duration_statement、log_checkpoints、log_lock_waits、log_temp_files 已开;pg_stat_statements 已装
  • initdb 数据校验和已开(新集群默认;老集群确认状态)
  • autovacuum 参数按最大表尺寸核算过,不是默认 0.2 一把梭
  • idle_in_transaction_session_timeout、max_slot_wal_keep_size、statement_timeout(按业务)已设
  • 备份计划:全量周期 + WAL 归档 + 保留策略 + 最近一次恢复演练日期
  • 复制拓扑:槽/保留策略、同步档位与 RPO 对齐,备库延迟有告警
  • 大表变更剧本含 lock_timeout 与回滚方案;CONCURRENTLY 失败有处置预案
  • 应用角色最小权限;DDL 账号与运行账号分离
  • 存量 SECURITY DEFINER 函数逐个核对:固定 search_path + 体内 schema 限定 + EXECUTE 收紧
  • 扩展清单过一遍准入三问(谁维护/要不要 preload/出事能否绕过);preload 类扩展的 shared_preload_libraries 现值留档
  • 关键业务已验证:隔离级别语义符合预期(尤其 RR 不防写偏斜)、错误码 40001/40P01 有重试
  • 翻页/导出/队列三类接口确认无深 OFFSET、无逐行往返
  • 监控面板含:容量、事务回绕 age、死元组、复制延迟、连接水位、慢查询 TopN
  • PG 大版本计划:18.x 小版本按月跟进;升级路线(pg_upgrade / 逻辑复制)演练过
  • Go/客户端依赖钉住版本(pgx v5.11.0 基线),驱动行为文档与手册版本一致

五、调试速查表

症状 第一反应 工具/查询 详见
连不上 看 listen_addresses → pg_hba → 防火墙/安全组 psql 报错码即知卡在哪层 00-安装与配置.md
认证失败 用户/库存在性与 pg_hba 首条命中 SELECT * FROM pg_hba_file_rules; 12-安全与权限管理.md
查询突然变慢 统计过期?计划回归?锁等待? pg_stat_activity 看 wait_event → EXPLAIN (ANALYZE, BUFFERS) 09-查询优化与EXPLAIN.md
查询卡在不动 谁堵谁 SELECT pg_blocking_pids(pid); 05-事务与锁.md
表"变大但数据没多" 死元组/索引膨胀 pg_stat_user_tables.n_dead_tup、pgstattuple 06-MVCC与Vacuum.md
全库读写报错 是不是回绕保护/盘满 age(datfrozenxid);pg_wal 目录大小 06-MVCC与Vacuum.md
备库延迟大 回放竞争/热查询冲突/WAL 网络 pg_stat_replication flush vs replay LSN 10-复制与高可用.md
应用报 40001/40P01 正常信号:并发冲突,缺的是重试 错误码见 05-事务与锁.md 处置节 05-事务与锁.md
导入慢 逐行 INSERT?索引太多?触发器税?vacuum 风暴? 改 CopyFrom;导入期临时建索引/禁触发器 08-Go客户端pgx.md、14-触发器.md
函数/表达式索引结果"过期" IMMUTABLE 错标 + 常量折叠/计划缓存复用 pg_get_functiondef 看波动性;EXPLAIN 看折叠点 13-函数与存储过程.md
触发器"没生效" DISABLE 状态、replica 会话、WHEN/UPDATE OF 没命中 pg_trigger.tgenabled;SHOW session_replication_role 14-触发器.md
“重启就好了” 别急,先留现场 pg_stat_activity 快照 + plan 导出再重启 09-查询优化与EXPLAIN.md

实战建议

这本手册读到这里,最值钱的习惯其实是三条:

  1. 参数默认值只对测试负责。 上线前把 pg_settings 里 source 为 default 的关键项过一遍——shared_buffers、work_mem、random_page_cost、autovacuum 组、日志组、超时组,每个都问一句"这台机器这个业务该是多少"。
  2. 把 MVCC 的账算在应用头上。 死元组、长事务、深分页、无效索引、uuid 打散写入,本质都是"你写的 SQL 在存储层留下的脚印"。会看 pg_stat_user_tables、pg_stat_activity、EXPLAIN 这三个视图,等于有了数据库的体检报告。
  3. 备份和权限按最坏一天设计。 复制会同步灾难,所以要有独立备份并演练恢复;注入会放大权限,所以角色要最小化并锁 TLS。出事时的差距,取决于你平安时做过哪件"无聊"的事。