由 John Doe 十月 29, 2026
在有开发者喊出 “一切皆用 PostgreSQL” 的口号以后,又有人开始告诫 “别什么场景都用 PostgreSQL” 了。
目录
作者:Oz
开门见山说句实在话:我写这个标题,纯粹是为了吸引你的注意力。事实上,PostgreSQL 绝对是软件工程领域的巅峰之作,堪称全球最健壮、最可靠的开源技术产品。如果你是独立开发者、单人搭建 MVP 的开发人员,或是正在寻找产品市场契合点的早期初创团队,把核心数据、缓存、分析、队列全都默认交给 PostgreSQL 处理,是非常聪明且务实的选择,完全可以继续这么做。
但我写这篇文章,是因为 “凡事都用 PostgreSQL” 的建议已经演变成了业界不容置疑的教条。当你的应用从几千用户增长到数百万用户时,把 PostgreSQL 当成 “万能金锤” 什么都敲,就会从务实的捷径,变成巨大的架构隐患。
本文不会只谈我个人的观点。我整合了多家将 PostgreSQL 用到性能极限的公司的故障复盘报告、技术白皮书与技术博客,从最真实的工程实践角度,解释为什么硬让关系型数据库同时承担消息中间件、OLAP 引擎、向量搜索引擎与时序数据库的工作,最终会拖垮你的系统架构。

一、MVCC 陷阱:PostgreSQL 不是消息队列
当初创公司需要处理后台任务(比如发邮件、Webhook 重试、视频处理)时,搭建 Kafka、RabbitMQ 或 Amazon SQS 总显得像是多余的运维开销。最省事的办法,就是在 PostgreSQL 里建一张任务表,加个状态字段,让工作节点轮询读取。
规模小的时候这么做没问题。但在高并发场景下,这种反模式会直接把数据库拖垮,罪魁祸首就是 MVCC(多版本并发控制) 。
在 PostgreSQL 中,执行UPDATE操作时(比如把任务状态从 “待处理” 改成 “处理中”),它不会直接覆盖磁盘上原有的行数据,而是写入一行全新的数据,再把旧行标记为 死亡元组(dead tuple) 。
如果有几十个工作节点持续轮询、认领、更新任务,每小时就会产生数百万个死亡元组。数据库的 自动清理(autovacuum,即垃圾回收进程) 最终会跟不上数据膨胀的速度。
Stripe 前资深工程师布兰德・利奇(Brandur Leach)曾在《用 PostgreSQL 做消息队列》一文中,对这个问题做过最知名的技术拆解:
PostgreSQL 的 MVCC 机制决定了每次更新都会写入新行。当它被用作高并发队列时,表膨胀会变得完全失控。索引出现碎片化,顺序扫描耗时越来越长,自动清理进程也会资源不足。最后你要消耗巨量磁盘 I/O 来处理垃圾回收,核心事务负载反而得不到足够资源。我们得出的结论是:PostgreSQL 是优秀的数据库,但做队列非常糟糕。 —— 布兰德・利奇,个人技术博客
GitLab 也遭遇过完全相同的危机。在他们公开的数据库不稳定故障复盘报告中,明确提到小表(比如 CI/CD 任务队列表)的高频更新引发了严重的表膨胀,最终迫使他们将队列类负载彻底剥离,迁移到了 Redis 上。
工程真相:如果数据生命周期极短(比如记录的创建、处理、删除都在几分钟内完成)就别放进 PostgreSQL。改用 Redis(搭配 BullMQ),或是 RabbitMQ 这类专业消息中间件。
二、写放大噩梦:优步的前车之鉴
把所有应用功能都塞进单个 PostgreSQL 单体库时,你不可避免会得到带几十个索引的大表。要按邮箱查用户、按组织查、按上次登录时间查、按 API 密钥查,于是就不停加 B 树索引。
很快,你就会撞上 写放大(Write Amplification) 的高墙。
在 PostgreSQL 里更新一行的单个字段时,哪怕这个字段没有建索引,数据库通常也要写入一整行新版本的数据。如果这张表有 15 个索引,PostgreSQL 还必须更新全部 15 个索引,让它们指向行数据在磁盘上的新物理位置。
这正是当年 Uber 弃用 PostgreSQL、转向 MySQL 的核心原因。在 2016 年那篇极具争议的工程文章《Uber 工程团队为何从 PostgreSQL 转向 MySQL》中,Uber 核心架构团队解释了高写入负载下 PostgreSQL 的残酷物理限制:
在 PostgreSQL 中,如果一张表有十几个索引,哪怕更新一个无关字段,也可能导致所有索引都要更新。这种 “写放大” 彻底拖垮了我们的磁盘 I/O。每一次小小的逻辑更新,都要往磁盘写入海量物理数据。以 Uber 的规模,硬件根本扛不住仅仅为了维护派单表索引所需的 IOPS。 —— Uber 工程团队
讽刺的是,就连基于 PostgreSQL 做时序数据库扩展的 TimescaleDB 团队,也发表过大量研究,说明标准 PostgreSQL 的 B 树在处理高频时序写入时性能会严重退化。当 B 树大小超过可用内存后,每一次插入都要从磁盘读取数据页、修改后再写回磁盘。
工程真相:如果是高写入量的遥测数据、事件日志,或是高频物联网数据,别再放进索引繁多的 PostgreSQL 表了。改用专为高吞吐追加写入设计的时序数据库,或是 DynamoDB、Cassandra 这类 NoSQL 方案。
三、分析场景与只读副本的误区
CTO 常说:“我们不需要数据仓库,搭个 PostgreSQL 只读副本,让数据分析师在上面跑查询就行。”
这套办法大概能撑半年。直到数据分析师要跑一条覆盖 5000 万行的复杂GROUP BY查询来计算月度经常性收入,这条查询要花 14 分钟才能跑完。
PostgreSQL 是 OLTP(联机事务处理) 数据库,采用行式存储引擎。当你执行类似SELECT SUM(amount) FROM payments的查询时,PostgreSQL 必须把整行数据(包括用户 ID、时间戳、JSON 元数据、Stripe 支付单号)全部从磁盘加载到内存,只为读取其中的金额字段。
大型开源分析平台 PostHog 决定将整套分析引擎从 PostgreSQL 迁移到 ClickHouse 时,其工程团队写过一篇非常精彩的技术分析:
PostgreSQL 处理事务数据非常出色,但它本质是行式存储。而分析场景往往只需要对一两个列做海量数据聚合,这时行式存储的效率就极低。迁移到列式数据库 ClickHouse 后,原来在 PostgreSQL 里要跑几分钟的查询,现在只需要几毫秒 —— 因为 ClickHouse 只会从磁盘读取查询所需的列,并且会对数据做高度压缩。 —— PostHog 技术博客
工程真相:别再用重型分析任务折磨你的 PostgreSQL 只读副本了。如果需要对上百万行数据做聚合计算,你应该用 OLAP(联机分析处理) 引擎。通过 CDC(变更数据捕获) 把数据同步到 ClickHouse、Snowflake 或是 BigQuery 中。
四、pgvector 的错觉:ACID 与 AI 天生不搭
过去两年,所有初创公司都在转向 AI。搭建 RAG(检索增强生成) 管线时,行业默认做法就是装个 pgvector 扩展,把向量嵌入和用户数据存在一起。
向量相似度搜索(比如 HNSW 分层导航小世界图)是和传统 CRUD 完全不同的负载类型。它需要把海量高维数组常驻内存,还要持续执行高 CPU 消耗的计算(余弦相似度、欧氏距离等)。
如果在处理用户认证的同一批 CPU 核心上跑重型向量搜索,两者就会争抢资源。长时间运行的向量搜索会把关键的关系型数据从 PostgreSQL 的 共享缓冲区(shared_buffers,即内存缓存) 中挤出去。一旦用户数据不在内存里,基础 API 查询就不得不走磁盘,导致整个应用的延迟飙升。
向量数据库 Qdrant 的一位资深工程师在近期的架构分析中,点明了这种本质上的不匹配:
把重型向量搜索和核心事务数据放在同一个引擎上运行,是运维的噩梦。你在强迫一个为精准性和 ACID 合规设计的系统,去做持续、繁重的概率计算。规模上来之后,向量索引构建会吃掉所有可用内存,把常规 CRUD 操作堵死。在这里,算力分离不是可选项,是必选项。 —— Qdrant 工程团队
工程真相:正视向量嵌入的本质,它是高度专用、计算成本极高的搜索索引。把元数据的权威数据源留在 PostgreSQL,但把实际的向量存储和 近似最近邻(ANN) 计算,交给 Pinecone、Milvus、Qdrant 这类专用引擎。
五、全文检索:tsvector 的能力边界
PostgreSQL 内置了全文检索能力(tsvector 和 tsquery)。开发者为了省去维护独立 Elasticsearch 集群的麻烦,会把整个搜索框功能都基于 PostgreSQL 来做。
它处理基础的词干提取和单词匹配确实很出色。但搜索从来不止是匹配单词这么简单。现代搜索要考虑相关性、容错拼写、结果排序。
当你要在数百万行数据上实现模糊匹配(三元语法 trigram),再叠加自定义排序算法(比如根据商品评分和新鲜度加权),PostgreSQL 的查询就会变得极其复杂,慢得难以忍受。
专业搜索引擎厂商 Algolia 的工程团队,解释了为什么关系型数据库终究做不好搜索:
关系型数据库用的是 B 树,它擅长精确匹配,但应对人类搜索的概率性需求时表现很差。专业搜索引擎使用倒排索引,针对 TF-IDF 或 BM25 评分算法做了优化。而且在关系型数据库中,把文本搜索和排序操作(比如按热度排序)结合时,数据库往往会完全忽略文本索引,转而执行全表顺序扫描。 —— Algolia 工程团队
工程真相:内部管理后台可以用 PostgreSQL 的 tsvector。但如果搜索是面向用户产品的核心功能,请改用 Elasticsearch、Algolia 或是 Meilisearch。
六、连接池瓶颈:Figma 的扩容难题
现代架构高度依赖 Serverless 函数(AWS Lambda、Vercel)或是成千上万个解耦的微服务。这种部署模式和 PostgreSQL 的连接架构存在根本冲突。
PostgreSQL 采用 “单连接单进程” 模型。每有一个微服务建立连接,Linux 内核就要 fork 出一个新进程。每个进程哪怕空闲,也要消耗约 10MB 内存。如果有 5000 个 Lambda 函数同时启动并访问 PostgreSQL,哪怕不跑任何查询,光是 CPU 上下文切换的开销就能把服务器压垮。
Figma 的工程团队写过一篇关于数据库扩容的精彩复盘。在最终转向水平分片架构之前,他们和连接数限制打过一场硬仗:
因为 PostgreSQL 每个连接都会生成一个新的操作系统进程,连接的创建销毁成本极高。随着微服务规模增长,我们遇到了瓶颈:数据库花在管理连接状态和上下文切换上的 CPU 周期,比实际执行 SQL 查询的还多。我们不得不搭建了大规模、多层级的 PgBouncer 集群,只为了挡住应用层的连接冲击,保护数据库。 —— Figma 工程团队
这也是为什么 Supabase 这类公司必须开发 Supavisor(用 Elixir 编写的新一代连接池),来应对原生 PostgreSQL 根本扛不住的 Serverless 连接洪峰。
结论:尊重架构边界
我不是让你放弃 PostgreSQL,而是让你尊重它的能力边界。
开发者的 “偷懒” 心态,把我们推向了 “一套方案解决所有问题” 的思维定式。我们想要单条连接串、单个 ORM、一套备份脚本的便利。但互联网需要的是快速、弹性、高可用的应用。你不可能只用一把瑞士军刀,造出一辆 F1 赛车。
如果想搭建一套能撑过 A 轮之后增长的系统,你必须采用 多语言持久化(Polyglot Persistence) 架构:
- 守住核心:把 PostgreSQL 严格用在它最擅长的领域:高度结构化、必须保证 ACID 合规的关系型数据(比如用户账户、账单、核心业务逻辑、外键约束场景)。
- 剥离短生命周期数据:把消息队列和后台任务交给 RabbitMQ 或 Redis。
- 解耦分析负载:把分析数据同步到 ClickHouse、Snowflake 这类列式 OLAP 数据库。
- 用专业的搜索 / AI 工具:把重型向量计算和全文检索,交给 Pinecone、Elasticsearch 这类专用引擎。
别再遇到什么问题都默认用 PostgreSQL 了。沉下心做好架构思考,为不同的工作选择合适的工具。你未来的值班工程师会感谢你的。