05 - 读写分离与缓存
索引、SQL、表结构优化,都是在「单个数据库内部」下功夫。如果单库已经优化到位,压力还是太大,下一步不是急着拆库,而是先借「外部力量」给数据库减负——缓存和读写分离。
一句话先记住:分库分表之前,先想尽办法「别让请求打到数据库」(缓存)和「把读分摊出去」(读写分离)。
(这两块在《服务端高并发优化》系列讲过原理,这里聚焦「它们在数据库优化路径里的位置」。)
5.1 缓存:让请求根本不到数据库
数据库最怕「反复查同样的数据」。缓存把热点数据放到 Redis(内存),绝大多数读请求在缓存层就返回了,根本不碰数据库。
没有缓存: 有缓存:
请求 → 数据库 请求 → Redis(命中,2ms)→ 返回
(每次都查) └ 未命中才 → 数据库 → 回填缓存
效果:数据库的读压力,可能直接降到原来的 1/10 甚至更低
- 适合缓存:读多写少、能容忍短暂不一致的数据(商品详情、用户信息、配置)
- 注意:缓存穿透/击穿/雪崩、一致性问题(见高并发系列第 03 章)
优先级:缓存通常是「单库优化之后」的第一选择,因为它直接砍掉大量读请求,成本又低。
5.2 读写分离:把读分摊到多个从库
大多数系统读远多于写。读写分离让写走主库,读分摊到多个从库:
应用
│
┌────────┴────────┐
写 │ │ 读
▼ ▼
┌─────────┐ ┌──────────┐
│ 主库 │─复制─▶│ 从库1 │
│ (Master) │─复制─▶│ 从库2 │ 读请求分摊
└─────────┘ └──────────┘
PostgreSQL 原生支持流复制搭建一主多从。
- 解决的是「读」的扩展:读能力随从库数量增加
- 最大的坑:主从延迟——刚写完立刻读从库,可能读到旧数据(对策:关键读走主库,见高并发系列第 06 章)
注意边界:读写分离只能扩展读,扩展不了写。如果瓶颈是「写太多」,读写分离救不了,那才要看分库分表。
5.3 分区表(Partitioning):分库分表的「轻量替代」
在真正「分库分表」之前,PostgreSQL 还提供了一个原生、且对应用透明的中间方案——分区表。
一张巨大的 orders 表(比如 5 亿行),按时间分区:
orders (逻辑上还是一张表,应用照常查)
├── orders_2023 (物理子表)
├── orders_2024 (物理子表)
└── orders_2025 (物理子表)
查 2025 年的订单 → PostgreSQL 自动只扫 orders_2025 这个分区
→ 不用扫全表(分区裁剪 Partition Pruning)
分区表 vs 分库分表:
| 方面 | 分区表 | 分库分表 |
|---|---|---|
| 位置 | 还在同一个数据库实例内 | 拆到多个库/多台机器 |
| 对应用 | 基本透明(还是查一张表) | 应用要感知,通常需中间件 |
| 解决 | 单表过大、按范围查询慢 | 单机容量/性能到顶 |
| 复杂度 | 低(数据库原生支持) | 高(跨库查询、分布式事务等) |
关键认知:如果你的问题是「单表太大导致查询/维护慢」,但单台机器还扛得住,那 PostgreSQL 分区表往往就够了,不必上真正的分库分表。分区表是「单机内的拆分」,代价小得多。
5.4 优化路径上的位置
把前面所有手段按顺序排一下,你就知道「分库分表」到底排在多后面:
数据库慢/扛不住,优化优先级:
① 索引优化 ← 最先,性价比最高(01-03 章)
② SQL / 表结构优化 ← 改写法和设计(04 章)
③ 缓存 ← 砍掉大量读请求(本章)
④ 读写分离 ← 分摊读压力(本章)
⑤ 分区表 ← 单表太大,但单机够用(本章)
──────────── 以上都不够了,才 ↓ ────────────
⑥ 分库分表 ← 最后的重武器(06-08 章)
核心思想:分库分表是「架构级手术」,会带来一堆新问题。前面五步能解决就绝不上第六步。 现实中,绝大多数系统在前五步就够了。
本章小结
| 你要记住 | 要点 |
|---|---|
| 缓存 | 让读请求根本不到数据库,砍读压力,成本低 |
| 读写分离 | 写主库读从库,扩展读能力;扩展不了写 |
| 分区表 | 单机内拆大表,对应用透明,代价远小于分库分表 |
| 优化路径 | 索引→SQL→缓存→读写分离→分区表→(最后)分库分表 |
下一章预告:如果前面所有招都用尽了还扛不住,才轮到分库分表登场。它到底解决什么问题?为什么说它是「重武器」?
上一章 ← 04 - SQL 与表结构优化 | 下一章 → 06 - 为什么要分库分表