初创公司 Postgres 生存实战指南
The startup's Postgres survival guide

过去半年,我整理了 Hatchet 团队两年间与 Postgres 搏斗的经验,写成这份生存指南。虽然官方文档很全面,但在系统告急时往往难以快速找到解决方案。本文从基础架构设计、读写查询优化、索引策略,到查询规划器的“漏桶”特性、自动清理机制(autovacuum)以及连接池管理,层层深入。无论你是刚接触 SQL 还是经验丰富的工程师,都能从中找到避免生产事故的关键技巧。特别提醒:如果你依赖 Claude 生成查询,这份指南可能显得多余;但若你希望真正掌控数据库性能,这里的内容值得反复研读。
查询规划器是那种最典型的‘漏桶抽象’:你几乎无法控制它,却必须理解它那些自发性甚至看似非理性的行为。
HN 评论区
218- ComputerGuru
一些补充和更正:
* 一般情况下使用 uuidv7 而不是 uuid(通常是 v4)
* 除了最小化锁定的记录数外,确保所有查询中的锁是确定性有序的(例如始终按 id 升序),否则你会遇到死锁(不过 Postgres 的死锁检测器非常强大,运气好的话你只会直接报错)
* 始终使用 explain (generic_plan),以便 a) 原样复制粘贴带有参数占位符的查询,b) 查看当 Postgres 无法看到具体参数值时查询实际会如何优化
* 测试查询计划时(尤其是表为空或接近空时),使用 set seqscan = off,这样你可以看到当顺序扫描不再廉价时索引是否会被使用
* 大家都默认使用 btree 索引,但它们比较重且会增加索引膨胀。如果你只需要按列/ID 查找,而不需要排序或获取大于/小于某个参数的值,请考虑改用 hash 索引。虽然无法创建唯一的 hash 索引,但你可以创建 exclude using hash 约束来达到相同效果(只是不支持多列唯一索引)
* 了解 GIN(和 GIST)索引。它们可以加速常见查询而无需新语法,这对来自 MySQL 的用户来说可能出乎意料;也就是说,你可以用它们加速像 '%foo%' 这样的普通查询,而无需切换到 FTS。
- theallan
使用数据库时,难道首要任务之一不应该是制定备份策略吗?我理解高可用(HA)在起步阶段是“锦上添花”,但如果你有一个生产数据库,备份和恢复计划理应出现在生存指南中吧?这里似乎都没提到。
大家用什么做 pg 备份?Barman ( https://pgbarman.org/) 还是主流方案吗?(我有一阵子没部署新的 pg 实例了,但正在为一个新项目考虑这个)。
- frollogaston
这条建议很好,但我合作过的每个初创公司遇到的更紧迫的问题甚至比这还要多。通常不是扩展性问题,而是组织层面的问题。通常能解决这些问题的是:
1. 不要用 ORM。
2. 使用序列主键(serial PKs),不要用有意义的字段(文章里提到了这点)。
3. 需要时使用 jsonb,但要节制。
4. 让你的单一事实来源(source of truth)只增不改(append-only),意味着你只插入,从不更新或删除。你可以有次要的非规范化表进行变更,但这只是为了性能/便利,不应是你的单一事实来源。
5. 使用连接池,但要留意你使用了多少连接。除非你搞砸了什么,否则你可能不需要 PgBouncer。
6. 在代码中,除非有明确理由,否则避免显式事务。通常只有非规范化部分才需要。只需从池中获取一个连接,做点事,提交,然后把连接还回池中。如果你要维持一个事务开启,中间千万不要做耗时操作,比如 RPC。我经常看到人们不加思考地让事务保持开启。编辑:另外,几乎永远不要使用 SERIALIZABLE 事务。
7. 如果你在使用显式锁(如 SELECT FOR UPDATE),那可能哪里出问题了。
8. 不要通过让单张表中的每一行根据“type int”枚举列代表多种含义来重新发明类型系统。这听起来有点具体,但不知为何总有人想这么干。
9. 同上,不要重新发明图数据库,通常是用“node”/“edge”表自外键关联来实现 o […]
- thundergolfer
总体是一篇好文章,有些评论:
> 对于低流量表,特别是数据库一致性和正确性很重要的地方,使用带级联删除的外键。高流量时要小心。
这可能只是我个人的看法,但我讨厌级联操作,原因很简单:在大多数地方,大多数开发者“生活”在与数据库交互的 Python/Node/Go/whatever 应用程序中,而不是数据库本身。级联删除(或更新)基本上就是魔法,很难理解“为什么删除表 A 的一行会自动删除表 B 的某行”。尤其是如果某人把级联设置错了!在我看来,为了长期的可维护性,最好发出显式的删除子句。正确使用外键将防止任何数据库一致性问题。
> 大表迁移的技巧
陷阱和变通方法都正确,但值得指出的是,已经有工具 [0] 可以帮你管理这些。对大表进行更改应该简单到只需运行一个命令(然后紧张地监控接下来的 24 小时,等待数据复制完成)。
其他需要考虑的事情,
1. 尽早习惯将应用程序和数据库部署分开。你无法同时事务性地部署模式更改和应用程序更改,总会有一段延迟导致数据库和应用程序版本不同步,最终你会遇到数据库更改部署成功但应用程序 […]
- mjr00
如果你有一个横向扩展的应用程序(数百个 API 服务器和异步工作器),你可能还需要一个连接池代理,比如 pgbouncer!并且为不同的连接配置(较低/较高的超时时间、读写分离)设置不同的池。目前关于这一部分的章节有点简略,但根据我的经验,调整和配置这些连接池器相当复杂,值得扩展一个章节!
我在其他评论中也看到有人提到这一点,但我强烈建议增加一个关于监控和告警的章节。光关于监控这一项,几乎就能写一篇同等长度的博客文章了 :)
- mrkaye97
作为一家早期依赖 Postgres 的初创公司的亲历者,我认为这篇文章在监控和告警方面的强调还不够。Postgres 有几个关键的故障模式是你永远不想发生的,你可以利用告警在危险发生前获得早期预警。
例如,AWS 会在你接近 XID 回绕(wraparound)时发送邮件。在初创公司,这封邮件很可能被忽略,尤其是如果它是在节礼日(Boxing day)发送的。你希望 AWS 监控并发送这封邮件的机制能连接到你的寻呼机(pager)。