本文目录导读:

数据库读写分离是提升系统性能的常用手段,但实践中确实有不少“坑”,如果设计或实施不当,可能带来比单库更严重的后果。
以下是常见的坑及应对方案:
数据一致性问题(最核心的坑)
- 主从延迟: 业务写入主库后,立刻去从库读取,但从库数据尚未同步完成,导致读到旧数据或空数据。
- 场景: 用户注册后立刻登录、下单后立刻查看订单详情、支付成功后刷新页面显示未支付。
- 后果: 影响用户体验,严重时导致重复下单、超卖等业务逻辑错误。
- 应对:
- 强制读主: 对一致性要求极高的操作(如支付回调、刚注册首次登录),在代码内标记为强制从主库读取。
- 二次确认: 写入主库后,短时间内在业务层进行轮询或等待,确认从库同步完成后再返回成功。
- 缓存辅助: 写主库的同时更新缓存,读请求优先读缓存(需注意缓存与数据库的一致性问题)。
- 查询最新时间戳: 在写入时记录一个时间戳,读从库时附带该时间戳,若从库最后同步时间早于此时间戳,则等待或切换读主库。
主库写入压力未真正转移(假读写分离)
- 现象: 读写分离只做了“读操作去从库”,但写操作(INSERT/UPDATE/DELETE)依然全部压在主库,如果业务是写密集型,主库依然会扛不住。
- 深坑: 很多复杂的查询(如带
ORDER BY、GROUP BY、大范围LIKE)其实是由页面操作触发的,但代码层面直接以“写”的逻辑执行,请求依然发往主库。 - 应对:
- 严格区分
select操作和update/delete/insert操作。 - 使用
select ... for update时,必须强制路由到主库(因为涉及行锁)。 - 不要将简单的“查询报表”类操作错误地归为写操作。
- 严格区分
事务内的读写分离陷阱
- 问题: 在一个事务中,先执行写操作(主库),再执行读操作(路由到从库),由于事务隔离级别(如
READ COMMITTED),在事务结束前,从库读不到主库刚写入但未提交的数据。 - 场景: 事务A插入一条订单,然后查询该订单,若查询走从库,可能查不到(数据尚未提交到从库)。
- 应对:
- 事务内强制读主: 在同一个事务中的
select语句,应统一路由到主库。 - 使用代理层: 如 ShardingSphere、MyCAT 等中间件,它们能识别事务上下文,自动将事务内的读请求路由到主库。
- 事务内强制读主: 在同一个事务中的
连接数与短连接风暴
- 问题: 应用层通常配置连接池(如 HikariCP),若读写分离配置不当,应用会为每个从库建立独立连接池,如果从库数量多(如5个从库),每个应用节点需要维护大量的长连接。
- 突发场景: 应用启动或代码发布时,所有应用实例同时建立大量连接到各从库,可能导致从库连接数瞬间打满(
max_connections耗尽)。 - 应对:
- 合理设置连接池大小(不要太大)。
- 使用连接池预热(启动时预创建少量连接)。
- 控制从库数量,并非越多越好。
- 使用数据库代理(如 ProxySQL、MaxScale)来统一管理连接。
负载均衡与延迟不均
- 问题: 简单的轮询(Round Robin)路由策略可能导致从库负载不均衡,某个慢查询落在特定从库,导致该从库延迟飙升,后面的读请求全部卡住。
- 场景: 一个从库因执行了某个大报表查询导致CPU高,若还继续给它发请求,整体性能会下降。
- 应对:
- 使用最小连接数或最快响应时间的负载均衡算法。
- 在应用层或代理层设置读写分离的健康检测:延迟超过阈值(如10秒)的从库自动摘除,不再分配读流量。
- 定时进行延迟监控,观察
Seconds_Behind_Master指标。
跨库JOIN与分库分表混淆
- 问题: 读写分离只解决主从,不解决水平拆分,若将读写分离与分库分表(Sharding)混为一谈,可能会尝试跨主从库进行
JOIN查询,导致性能极差或无法查询。 - 场景: 订单在主库,用户信息在从库,直接写
JOIN会导致全表扫描或跨库传输数据。 - 应对:
- 明确读写分离不解决数据分区问题。
- 避免跨库
JOIN,改由应用层做组合查询(先查A库,再查B库)。 - 若必须
JOIN,考虑使用全局表或数据冗余(如将用户基础信息同步到订单库)。
“幽灵读”与业务逻辑混乱
- 现象: 主库写入一条数据后,该数据被后续的判断逻辑(如唯一性检查)过滤掉,但读从库时该数据尚未同步,导致重复插入。
- 例子: 用户抢购时,主库插入了一条购买记录,后续逻辑检查“是否已购买”,读从库发现没有,于是又插入一条抢购记录——导致超卖。
- 应对:
- 所有写入前的唯一性检查(如防重、库存检查),必须走主库。
- 业务上对幂等性要求高的操作,建议在数据库层面加唯一索引兜底。
核心避坑清单
| 坑点 | 核心原则 | 工具/方法 |
|---|---|---|
| 强一致性读 | 强制读主库 | 代码注解、AOP切面、ShardingSphere HINT |
| 事务内读写 | 事务内读走主库 | 事务绑定主库连接 |
| 延迟敏感 | 业务容忍度降级 | 缓存、等待同步、二次校验 |
| 连接管理 | 控制连接池大小 | 数据库代理(ProxySQL/MyCAT) |
| 负载均衡 | 按健康/延迟动态分配 | 中间件健康检查、摘除高延迟节点 |
一句话建议: 对于95%的互联网业务,推荐使用数据库中间件(如 ShardingSphere-JDBC、MyCAT、ProxySQL)来管理读写分离,而不是直接在代码中硬编码判断,中间件能自动处理事务绑定、连接池管理、延迟检测等复杂逻辑。