cswork / 系统设计

第 30 章 / 共 57 章 · 约 3 分钟

关键技术:PostgreSQL

本页目录
  1. 1. 核心模型
  2. 2. 什么时候用
  3. 3. 什么时候不适合
  4. 4. 关键限制和数量级
  5. 5. 面试中常被追问的点
  6. 6. 和其他技术的取舍
  7. 7. 本站案例
  8. 章节测验

PostgreSQL 是系统设计面试里最稳妥的默认数据库:ACID 事务、约束、JOIN、各种索引都很成熟,单机能扛的量比很多人以为的大。除非题目给出明确理由(写入量远超单主、访问模式极简单、需要全文相关性排序),先选它,再说清楚什么时候要换。

1. 核心模型

  • 关系模型和约束:主键、外键、唯一约束、CHECK 约束都由数据库保证。防止重复下单、重复订座,往往一条唯一约束就够了。
  • MVCC:每次更新写出新版本,读不阻塞写、写不阻塞读。旧版本由 VACUUM 回收。
  • 隔离级别:默认 Read Committed;Repeatable Read 在 PostgreSQL 里是快照隔离;Serializable 基于 SSI,冲突时让事务失败,由应用重试。
  • 行锁:SELECT ... FOR UPDATE 锁住要改的行;FOR UPDATE SKIP LOCKED 让多个 worker 从一张表里各取不同的任务,是用数据库做任务队列的常见写法。
  • 索引:B-tree 覆盖大多数等值和范围查询;GIN 用于 JSONB 和全文检索;GiST 配合 PostGIS 做地理查询;BRIN 适合按时间追加的大表。
  • WAL 和复制:所有修改先写预写日志(WAL)。流复制把 WAL 发给只读副本,默认异步,也可以配置成同步。

2. 什么时候用

  • 需要事务和约束的核心数据:用户、订单、支付、库存、预订。
  • 关系复杂、查询多变:后台报表、运营查询、需要 JOIN 的场景。
  • 中等规模的一切:在合理的索引和硬件下,单个主库每秒几千到上万次写事务、更多的读,数据量到几 TB 都很常见。
  • 一库多用:JSONB 存半结构化字段,PostGIS 做附近搜索,tsvector 做简单全文检索,SKIP LOCKED 做任务队列,很多时候不用再引入新组件。

3. 什么时候不适合

  • 写入远超单主能力:每秒持续几十万次写入(例如海量设备上报、聊天消息全量存储),单个主库撑不住,要么分片,要么换 Cassandra / DynamoDB。
  • 全文相关性排序和复杂搜索:模糊匹配、同义词、按相关性打分,用 Elasticsearch。
  • 大规模分析:在几十亿行上做聚合,用列式数仓(BigQuery、Snowflake、ClickHouse)。
  • 跨地域多活写入:单主架构下,远地域写入要跨洋往返;需要多地域强一致写入时考虑 Spanner、CockroachDB 这类分布式 SQL。

4. 关键限制和数量级

维度要记住的事
写入单主;每秒几千到上万次写事务是常见量级,具体取决于事务大小和硬件
读扩展加只读副本;异步复制有延迟(通常毫秒到秒级),刚写完马上读可能读不到
连接每个连接是一个进程,几百个连接就要用 PgBouncer 之类的连接池,不能让上万个实例直连
数据量单机几 TB 可以;更大或更快增长时,按时间做分区表、归档冷数据,或者分片(Citus、应用层分片)
热点行所有请求都更新同一行(全局计数器、热门商品库存)会在行锁上排队

5. 面试中常被追问的点

  • 怎么防止同一个座位被卖两次? 最简单的是条件更新:UPDATE seats SET status='held', hold_id=? WHERE id=? AND status='available',影响行数为 0 就说明被别人抢了;也可以用唯一约束或 SELECT ... FOR UPDATE。详见票务抢购案例。
  • 读副本的延迟怎么处理? 刚写完的用户短时间内读主库(read-your-writes),或者带上写入时的 LSN,副本追上后再读。
  • 这个查询为什么慢? 看执行计划(EXPLAIN ANALYZE),按 WHERE 和 ORDER BY 建复合索引,注意列的顺序。
  • 什么时候分片、按什么分? 先用尽索引、缓存、读副本和分区表;分片键要让最常见的查询只落在一个分片上,例如按 tenant_id 或 user_id。
  • 全局计数器成了热点? 拆成 N 行分片计数,读时求和;或者先在 Redis 或流处理里聚合,定期落库。

6. 和其他技术的取舍

选项适合代价
PostgreSQL / MySQL事务、约束、灵活查询写入受单主限制,分片要额外工作
DynamoDB / Cassandra海量写入、按 key 访问没有 JOIN,要按访问模式设计表
Spanner / CockroachDB跨地域强一致的 SQL写延迟更高,成本和复杂度更高
列式数仓大规模分析不适合高频小事务

面试里的表述可以是:“先用 PostgreSQL,因为这里需要事务和唯一约束;如果写入量增长到单主撑不住,再按 X 分片或把这张表迁到宽列存储。”

7. 本站案例

章节测验

5 道选择题
  1. 多个 worker 要从同一张 jobs 表里取任务,彼此不重复、也不互相等待。PostgreSQL 里常用哪种写法?
  2. 用户发帖后立刻刷新,却看不到自己的帖子。读请求走的是异步复制的只读副本。最直接的修法是?
  3. 下面哪个需求是换掉 PostgreSQL、改用宽列存储最有力的理由?
  4. 抢票时用“UPDATE seats SET status='held' WHERE id=? AND status='available'”占座,怎样判断是否抢到?
  5. 一个服务有 2000 个实例,每个实例都要连 PostgreSQL。主要风险和对策是什么?

章节讨论

正在加载讨论…