由 John Doe 十月 30, 2026
2026 年 10 月,Postgres 社区周报 第 2 期,本周新闻速递。
目录

Postgres 19 延期了,这是 AI 的错
- PostgreSQL 19 很可能推迟到 11 月发布,晚于以往 9 月中旬至 10 月中旬的惯例;多个大型特性在 beta 后期被回滚。
- 主因是 LLM 让 bug 发现速度远超人工修复速度:大量用户用 LLM 找 bug 并报告,维护者忙于修复。
- 回补补丁数量激增:同期回补提交增长超 60%,对旧稳定分支的回补增长超 74%。
- 修复仍需人工审查和提交;LLM 生成的补丁不一定正确或符合 PostgreSQL 标准,开发者被进一步摊薄。
- 原本用于审阅发布内容的时间被安全问题和分支 bug 修复占用,导致较晚才决定回滚哪些特性,最终造成发布延期。
- 短期延期不令人担忧,体现项目对质量的重视;长期挑战是:几十年积累的 bug 正被 AI 快速发现,如何追上并持续创新尚不明确。
- AI 将迫使 PostgreSQL 及所有软件开发者提高标准:未来每个功能都会被 AI 审查,这既是挑战也是机会。
将 74GB 数据迁出 RDS,并保留一周回滚窗口
- Prisma 将控制面数据库从 Amazon RDS 迁移到 Prisma Postgres,涉及 83 张表、74GB 生产数据,全程零停机、未暂停写入。
- 迁移采用 PostgreSQL 逻辑复制,分为四阶段:全量复制、数据校验、单次部署切换、保留一周反向回滚能力。
- 迁移前重点检查:源库启用逻辑复制、所有表有主键、目标库 schema 一致且表为空、网络连通、目标库保持活跃;
_prisma_migrations表因目标库已执行迁移而未复制。 - 数据复制完成后,用跨库查询比对所有表行数,并对低频变更表做 md5 校验;行数完全一致,磁盘大小差异来自压缩算法不同。
- 切换核心原则:所有服务和每条连接路径必须在同一次部署中全部切到新库,避免跨库读写不一致。
- 回滚方案:切换前几分钟在新库创建逻辑复制槽,RDS 静默后反向订阅 Prisma Postgres,并设置
copy_data=false、origin=none,保留一周可回滚窗口。 - 逻辑复制不传输 schema 变更,因此迁移期间每次 migration 必须同时应用到 RDS 和 Prisma Postgres;切换当天曾因 CI 任务误指向新库导致反向复制暂停,后手动补 SQL 恢复。
- 经验总结:按表名发布、排除迁移历史表;提前测试网络路径;用只返回差异的查询校验;一次性切换所有连接路径;回滚槽临近切换再创建。
- 改进点:修改数据库连接配置前,先确认哪些服务和 CI 任务使用该配置;大表保留策略应在复制前上线,以减少迁移数据量。
创业公司的 Postgres 生存指南
- Schema 设计最重要:上线后表结构最难改,应迭代设计;主键必用,时间字段用
timestamptz,低写入量场景可用外键和级联删除,高并发时要谨慎。 - 读查询优先走索引:按主键、唯一约束或显式索引过滤通常很快;否则可能触发全表扫描。小表全表扫描可能无感,大表要避免。
- JOIN 要重视索引:内连接通常应基于主键;
ON条件和WHERE条件一样需要索引支持。 - 列表查询用复合索引:常见慢查询是“大表分页/排序”,复合索引应让
ORDER BY列靠后,并尽量与索引列顺序一致。 - 写事务要短、锁要少:不要在事务中调用外部服务;只锁必要行;大表建索引用
CREATE INDEX CONCURRENTLY,避免阻塞写入。 - 迁移尽量可回滚、少阻塞:优先增量迁移并在事务中执行;警惕
ALTER TABLE、加约束等操作的锁表风险。 - 连接要复用,避免连接风暴:连接开销高,建议用外部连接池如 pgbouncer,或用内存连接池如 pgxpool。
- 理解查询计划器:它是“漏水的抽象”,依赖表统计信息;统计过期会导致计划异常,需关注
ANALYZE和 autovacuum。 - 慢查询用
EXPLAIN ANALYZE调试:对比估算行数和实际行数;生产环境谨慎执行ANALYZE,可先用EXPLAIN看计划。 - 有时全表扫描是合理的:索引扫描有额外开销,查询计划器可能判断顺序扫描更便宜;必要时考虑重构查询或分区。
- 批量写入能显著提升吞吐:把多行打包成批次发送,减少往返和锁开销,文中提到可约 10 倍提升。
- 高写入场景必须调 autovacuum:默认设置可能跟不上,导致死元组堆积、表/索引膨胀,甚至事务 ID 回卷造成宕机。
- 高级技巧很实用:
FOR UPDATE SKIP LOCKED适合队列和租约;分区利于时序数据和快速删旧数据;大表迁移可用触发器 + 分批回填,避免长事务。
Postgres 能扩展吗?
- 作者对单台 Postgres 服务器进行写入扩展性基准测试,结论是 Postgres 的扩展能力超出预期。
- 在 AWS RDS
db.m7i.24xlarge(96 vCPU、384 GB 内存、12 万 IOPS)上,单台 Postgres 可稳定达到 14.4 万次写入/秒,约合 每天 120 亿次写入。 - 原始写入瓶颈主要在 WAL 刷盘:Postgres 通过组提交把 WAL 同步写入磁盘,因此同一时刻通常只有一个进程在刷 WAL,其他进程等待 WAL 锁。
- 直接启动持久化工作流时,单台服务器可处理 4.3 万个工作流/秒,相当于约 8.6 万次 Postgres 写入/秒;瓶颈仍是 WAL。
- 使用 Postgres 作为持久化队列时,单台服务器约处理 1.21 万个排队工作流/秒,瓶颈变成
workflow_status表上的锁竞争。 - 将队列拆分为多个队列/分区后,排队工作流吞吐可提升到 3.06 万个/秒,约为直接启动工作流的2/3;此时瓶颈重新回到 WAL。
- 总体而言,单台 Postgres 足以支撑大多数应用场景;若需要更高负载,可通过分片到多台 Postgres 服务器继续扩展。
为分析调优 PostgreSQL — PGConf.EU
- 核心问题:生产库通常为 OLTP 日常业务优化,而分析查询(OLAP)通常复杂、耗资源、运行时间长;混合负载(HTAP)容易导致分析慢、业务也变慢。
- 分析查询影响业务的原因:大表全表扫描会挤占缓冲缓存,CPU/IO 与业务查询竞争,长事务还可能持锁并阻塞 vacuum。
- 配置与连接管理:使用连接池控制连接数;为分析用户单独设置角色、连接池和参数,如提高
work_mem、设置statement_timeout,避免分析会话占用过多资源。 - 索引优化:为高频分析条件或表达式创建索引,可显著提升查询性能,但索引会增加写入维护成本,需要平衡业务写入与分析需求。
- 预计算:使用生成列(generated columns)存储常用计算表达式;使用物化视图预聚合、预排序数据,并按需定时刷新,适合可容忍一定数据延迟的分析场景。
- 隔离分析负载:可将分析查询放到物理复制只读副本;若需更灵活地建索引、用户和物化视图,可用逻辑复制建立独立分析库。
- 查询外部数据:如需查询 Iceberg、Parquet 等湖仓数据,可用
pg_lake扩展,让 PostgreSQL 结合湖仓格式和 DuckDB 查询引擎,在同一 SQL 中联合在线数据与外部数据。 - 选择策略:若查询模式固定,优先考虑生成列、物化视图和索引;若需隔离,使用独立分析角色/连接池;若需彻底卸载负载,使用副本;若有湖仓数据,考虑
pg_lake。关键是根据实时性要求、隔离需求和业务影响做权衡。
一个只读副本如何差点拖垮主数据库
- 回填数据必须分批并限流:读副本同步落后会导致主库保留大量 WAL,磁盘从约 5 GB 膨胀到 31.7 TB,逼近 60 TB 上限。要同时监控副本延迟和保留 WAL,限制并发,并确保任务可暂停、可重试、可续跑。
- DDL 要设置短锁超时并重试:给繁忙表加外键等操作可能被 autovacuum 阻塞,导致后续查询排队、连接耗尽。建议使用约 250 ms 到 1 秒的
lock_timeout,配合有界重试、退避和抖动;注意lock_timeout只限制等待锁,不限制持锁时间。 - 模式变更要分阶段兼容部署:滚动发布期间新旧版本会共存,ORM 也可能仍引用已删除字段。应先部署不再使用该字段的代码,再在后续迁移中删除物理列;大变更按“新增结构—迁移数据—切换读写—移除旧结构”分步进行。
- 连接预算要按切换过渡期计算:连接池变更时,旧连接未释放、新连接又建立,连接数可能比稳态高约 40%。要按“活跃进程 × 池大小 + 后台任务 + 发布峰值 + 未排空旧连接”估算,并考虑连接创建速率和自动扩缩容带来的额外连接。
- 查询计划要用真实数据验证:测试库数据太少会掩盖全表扫描等昂贵计划。除了 SQL 和
EXPLAIN,还要评估请求或后台任务调用该查询的频率;高并发下单个查询成本会被放大。 - 把经验转化为自动化控制:用迁移 linter 检查已知危险模式,用监控告警观察查询耗时、连接池队列、vacuum、副本延迟、保留 WAL 和磁盘增长;同时跨层审计超时设置,并验证告警和修复确实生效。
转储/恢复完成,然后呢?
- pg_dump/pg_restore 成功不等于数据库已可上线:行数一致、应用能连接只是第一步;逻辑恢复通常不会带上列统计信息和可见性映射,查询可能因此极慢。
- 恢复后必须做两类维护:
ANALYZE补齐规划器统计,VACUUM建立可见性映射。案例中同一查询在恢复后耗时 32.6 秒,ANALYZE后 6.1 秒,VACUUM ANALYZE后 23 毫秒,约快 1,400 倍。 - PG18 的
pg_dump --statistics可传列统计,但不传可见性映射;PG19 中普通读查询还可能自行设置可见性映射位,导致首次查询更慢并产生大量 WAL。因此更应在放流量前执行VACUUM ANALYZE。 - 不要依赖 autovacuum 兜底:触发时间不确定,而且 autovacuum 不会收集分区父表的统计信息;分区父表需要手动
ANALYZE。 - 迁移 runbook 应明确加入维护步骤:流量前执行
VACUUM ANALYZE,可选FREEZE;大库使用vacuumdb --jobs;部分恢复只处理实际写入数据的表;切换前用EXPLAIN (ANALYZE, BUFFERS)检查关键查询计划和Heap Fetches。 - 物理备份恢复不受此问题影响;若可原地升级,
pg_upgrade可保留统计信息和可见性映射。文档和pg_restore本身通常不会提示这些后续步骤。
备份全绿,不代表你能恢复
- 核心观点:备份任务成功只说明“任务跑完了”,不等于“需要时一定恢复得了”。真正的风险在于“有备份”和“能恢复”之间的差距。
- 绿色备份的盲区:它不会验证 WAL 是否完整、文件页是否健康、非数据目录依赖是否齐全、是否能按时间点恢复,也不保证恢复耗时满足业务承诺。
- 常见恢复失败原因:WAL 归档链断裂;损坏数据被原样备份;保留策略不匹配;角色、配置、扩展、证书等缺失;加密密钥丢失;没有可用恢复主机;没人会恢复。
- 事故模式:误删数据后才发现无恢复主机、WAL 缺失,最终只能恢复到数天前并手工补数据,业务预期一小时却耗时两天多。
- 腐坏与保留期:保留 30 天不等于 30 天保护;若损坏页早已存在,保留期内所有备份可能都已污染。
- 归档监控要独立:备份任务绿不代表归档正常。应分别监控归档失败次数、最新成功归档段的时间、
pg_wal大小。 - 唯一有效验证:实际恢复文件、启动集群、回放到指定时间点,并验证数据正确性。
- 行动建议:独立监控归档链;确认备份与 WAL 的保留期覆盖承诺;记录数据目录外所有依赖及位置;选一个数据库,在干净机器上由他人操作,恢复到上周某个时间点。
PostgreSQL 中的 SQL 与 PL/pgSQL:计划缓存
- 问题现象:在 PostgreSQL 16 压测中,一个每小时运行、调用简单 SQL 函数 1000 万次的任务,单次占用约 47 秒 CPU。
- 修复效果:仅将函数从
LANGUAGE sql改为LANGUAGE plpgsql,函数内查询不变、作业不变、结果不变,CPU 时间降至约 24 秒,约减半。 - 根本原因:该 SQL 函数因使用集合返回函数
regexp_matches无法被内联;PostgreSQL 16/17 中 SQL 函数没有持久计划缓存,且循环中用SELECT ... INTO调用会让每行都成为新语句,导致每行都重新解析、生成计划并执行。 - PL/pgSQL 优势:PL/pgSQL 会缓存执行计划:前 5 次按实际参数生成计划,第 6 次生成通用计划,之后复用;该案例中共生成 6 次计划。
- 调用方式很关键:普通赋值
v_code := get_error_code(...)可让 SQL 函数只计划一次;而SELECT ... INTO、PERFORM、EXECUTE会为每行启动新查询。 - 更优方案:若改用
regexp_match替代regexp_matches,SQL 函数可被内联,避免函数级重复计划,CPU 时间进一步降至约 11.9 秒。 - 验证方法:启用
pg_stat_statements.track = all和track_planning = on可观察计划次数;案例中 SQL 版本执行 1000 万次、计划 1000 万次,PL/pgSQL 版本执行 1000 万次、仅计划 6 次。 - 结论:PostgreSQL 16 中不可内联的 SQL 函数可能因重复生成计划造成高 CPU;改为 PL/pgSQL 或改写为可内联函数,是无需改动整体系统的有效优化手段。
Postgres 中唯一可扩展的删除方式是 DROP TABLE
- 大规模
DELETE不是“完成工作”,而是“增加工作”:MVCC 会产生死元组、vacuum 债务、写与复制开销,并增加读查询和 autovacuum 负担;通常也不会立即把空间归还操作系统。外键CASCADE甚至可能让单行删除级联删除大量数据。 DROP TABLE/TRUNCATE虽然需要AccessExclusiveLock,但开销基本与数据量无关:直接从操作系统删除文件,只扫描缓冲区元数据;不产生死元组、无 vacuum 债务、对读者零额外工作,并立即释放空间。- 一次性清理大量垃圾数据可用事务性 DDL:
BEGIN→ 对表加ACCESS EXCLUSIVE锁 → 将保留数据放入临时表 →TRUNCATE原表 → 从临时表插回;WAL 只记录重新插入的行。 - 如果不能长时间持有排他锁,可用触发器把写入镜像到新表,再原子重命名切换。
- 当保留数据远多于待删数据、但待删数据仍不少时,采用小批量循环删除(如每批 1 万行),缩短事务、避免锁堆积,并让 autovacuum 跟上。
- 分区是把“大量 DELETE”转化为“偶尔 DROP TABLE”的关键手段:按日期分区并定期删除旧分区(可用
pg_partman);分区可递归,例如先按 LIST 分区,再对子表按 RANGE 老化。 pg_squeeze/pg_repack可处理已有膨胀,但更好的策略是通过 schema 设计避免大规模批量DELETE。
PostgreSQL 与 OOM killer:我们为什么使用严格内存过量提交
- PostgreSQL 不能容忍 OOM killer:postmaster 发现任一子进程被杀后,会认为共享内存可能已损坏,于是主动终止所有 backend,导致所有连接中断、事务回滚,并触发崩溃恢复。
- 因此,单次 OOM 可能造成整库级故障和较长停机时间;这也是团队坚持为 PostgreSQL 启用严格内存过量提交(strict overcommit)的原因。
- 启用严格 overcommit 后出现异常:机器仍有空闲物理内存,但数据库报 OOM。检查发现 8GB 机器的
Committed_AS高达 651GB,而实际可计入内存仅 2.43GB,约 648GB 是“幽灵”提交内存。 - 排除了 hugetlb / shared_buffers 重复计数等假设;全集群分析显示 Linux 6.5 内核的
Committed_AS膨胀明显且随 uptime 增长,6.8 内核无此相关性。 - 根因是 Linux 6.5 的一个提交改变了
do_vmi_align_munmap()返回值约定,而mm/mremap.c中move_vma()的错误判断从< 0误改为!,导致成功mremap时也错误地重新计入内存,vm_committed_as持续泄漏。 - Linus 用一行修复恢复了
< 0判断。该 bug 在默认 heuristic overcommit 下因Committed_AS仅作参考而隐蔽;在严格 overcommit 下,会计数漂移最终触发分配失败。
pg_pinecone:Pinecone 为 pgvector 列打造的向量索引
- 发布与定位:pg_pinecone 是 Apache 2.0 开源 PostgreSQL 扩展,直接为现有 pgvector 向量列建索引,数据留在 Postgres 内,无需额外运行第二套系统。
- 三种引擎:默认
native在库内构建和搜索,无需 Pinecone 账号、API Key 或网络调用;pgvector复用现有 HNSW;remote将搜索转到 Pinecone 托管服务。通过pinecone.set_engine()切换,SQL 查询不变。 - 核心机制:
native不用图索引,而用 IVF 聚类 + RaBitQ 压缩码(约每维 1 bit 加校正值)。搜索时先找最近聚类,用压缩码筛候选,再用全向量重排,因此索引更小、构建更快、内存占用更低。 - 过滤搜索更准确:在搜索过程中过滤,而非先找近邻再过滤;按过滤选择性选择预过滤或单阶段过滤,窄过滤场景也能返回完整结果,避免 HNSW 后过滤漏结果。
- 基准亮点:1M×768 维数据下,
native索引 136MB,HNSW 为 2.0GB(halfvec)/4.1GB(全量);0.95 recall 查询 1.3ms,对比 1.7/2.7ms;0.99 recall 为 2.3ms,对比 4.7/6.7ms;构建也更快。10% 过滤命中时,native达 1.000 recall(12.3ms),HNSW 迭代扫描最高 0.920(5.9ms)。 - 可选 Pinecone 功能:
remote引擎用于横向扩展;embed()可在 SQL 中把文本转嵌入;支持 BM25 全文检索与向量检索并用;rerank()可重排 Top 结果。 - 使用方式:可从 GitHub release 或 Docker Hub 安装,沿用现有 SQL(如
ORDER BY embedding <=> $1 LIMIT 10)查询,并可按tenant_id过滤;首版已发布,欢迎反馈和贡献。