数据库读写分离如何设计?会带来哪些一致性问题?
简化版
读写分离通常是主库承担读写中的写请求,从库分担读请求,用来提升读吞吐和隔离查询压力。它的核心问题是主从延迟:写成功后读从库可能读到旧数据,所以写后读、事务内读、强一致读通常要走主库。
详细版
读写分离设计要点:
- 写请求走主库;
- 普通非关键读可以走从库;
- 写后立即读、事务内读、强一致读走主库;
- 从库可按业务隔离,比如报表从库、只读从库;
- 监控复制延迟,延迟过高时读主或降级;
- 客户端或中间件要识别事务上下文;
- 故障切换后要更新主库路由。
读写分离解决的是读压力,不解决单条慢 SQL;慢查询打到从库也会拖慢复制,甚至导致延迟扩大。
完整版教学
一、读写分离的前提是复制
读写分离通常基于主从复制。主库接受写入,从库通过日志复制同步数据。应用层或数据库代理根据 SQL 类型和一致性要求把请求路由到主库或从库。
它适合读多写少的系统,比如内容展示、商品查询、资讯列表。但如果系统写多读少,收益就有限。
二、主从延迟是核心矛盾
复制不是瞬间完成的。主库提交成功后,从库可能还没重放到这条变更。
典型问题:
- 用户提交订单;
- 写主库成功;
- 订单列表读从库;
- 从库还没同步,用户看不到刚提交的订单。
这不是 bug,而是异步复制和读写分离的天然结果。
三、哪些读必须走主库
常见强制读主场景:
- 写操作之后短时间内的读;
- 同一个业务事务内的读;
- 支付、库存、订单状态等强一致数据;
- 管理后台刚修改后立即预览;
- 从库延迟超过阈值时。
有些系统会在用户写入后给会话打标,接下来几秒内该用户读请求走主库,这就是会话一致性的一种实现。
四、从库不是慢查询垃圾桶
很多人以为复杂查询丢到从库就安全。实际上从库也要执行复制任务。如果报表查询长时间占用 CPU、I/O 或锁资源,复制延迟会变大,读到旧数据的概率也变高。
更合理的做法是把报表库、搜索系统、OLAP 系统和在线只读从库分开,避免互相影响。
五、路由层要理解事务
不能简单看到 SELECT 就发从库。事务里可能先写后读:
begin;
update account set balance = balance - 100 where id = 1;
select balance from account where id = 1;
commit;
这个 select 必须读主库,否则就不符合事务内一致性。
六、高可用切换也会影响读写分离
主库故障后,需要提升新主库,并让应用或代理更新路由。如果路由更新不及时,可能写到旧主或读到错误节点。
所以读写分离不是单纯加几个从库,还要配合故障检测、主库选举、连接切换和延迟监控。
设计路由时最好把“读一致性级别”变成明确规则,而不是让开发凭感觉选择数据源。例如普通列表默认读从库,支付结果、库存扣减、订单创建后 5 秒内读主库;复制延迟超过 1 秒时摘除对应从库。规则越明确,线上问题越容易定位。
七、常见误区与追问
| 读请求类型 | 推荐路由 | 原因 |
|---|---|---|
| 首页、列表、非关键展示 | 从库 | 可接受短暂旧数据 |
| 写后立即查询 | 主库 | 需要读到刚写入的数据 |
| 事务内查询 | 主库 | 保持事务上下文一致 |
| 从库延迟超过阈值 | 主库或降级 | 避免旧数据扩大业务影响 |
记忆钩子:读写分离的难点不是“SELECT 去从库”,而是识别哪些 SELECT 不能去从库。
用时间线看主从延迟:
T0 主库提交订单 order_id=1001
T1 应用返回创建成功
T2 用户刷新订单列表,读从库
T3 从库 800ms 后才重放到 order_id=1001
如果用户在 T2 查询,就会看到“订单不存在”。所以很多系统会在写成功后给当前用户会话设置 2 到 5 秒读主窗口,或者根据复制延迟动态决定是否读主。
- 误区:读写分离能解决所有数据库性能问题。 它主要扩展读吞吐,慢 SQL、坏索引和大事务仍然要单独治理。
- 误区:所有 SELECT 都可以走从库。 写后读、事务内读、强一致读和延迟异常时都应该读主。
- 误区:从库可以随便跑报表。 重报表会抢 CPU/I/O,拖慢复制,导致更多业务读到旧数据。
- 追问:如何缓解写后读不一致? 会话读主、关键接口强制读主、延迟监控动态路由、业务提示稍后刷新都可以组合使用。
- 追问:主从延迟怎么监控? 关注复制位点差、延迟秒数、Relay Log 重放状态,以及业务层写入后可见性探测。
- 追问:故障切换后有什么风险? 路由可能还指向旧主或落后从库,需要代理、连接池、配置中心和应用缓存一起切换。
八、加强记忆
读写分离提升读吞吐,但代价是主从延迟和路由复杂度。普通读可以走从库,写后读、事务内读、强一致读走主库;从库也要保护,慢查询会拖复制。它解决读压力,不解决烂 SQL。