主从复制是读写分离的基础设施,必须确保同步稳定、监控延迟、隔离连接池并明确一致性策略。

主从复制是读写分离的基础设施
没有稳定的主从同步,读写分离就是空中楼阁。MySQL 的 CHANGE MASTER TO、PostgreSQL 的流复制(primary_conninfo 配置)、或者基于 GTID 的自动故障切换,都必须先跑通。常见错误是忽略复制延迟:应用刚写入主库,立刻在从库查不到新数据,表现为“写后即查失败”。这不能靠加 SLEEP 解决,得用同步策略兜底——比如对强一致性场景,强制走主库;或在事务内标记“本次查询需强一致”,由中间件路由到主节点。
关键点:
- 监控
Seconds_Behind_Master(MySQL)或pg_stat_replication(PostgreSQL),超过阈值时自动降级读请求 - 避免在从库执行
INSERT或UPDATE,否则主从冲突会中断复制 - DDL 操作必须在主库执行,且需确认已同步完成再切流量
中间件层路由逻辑决定成败
ShardingSphere、MyCat、Vitess 或自研代理,核心都是解析 SQL 类型 + 维护连接池 + 判断事务上下文。不是所有 SELECT 都能发给从库:SELECT ... FOR UPDATE、子查询含 INSERT、或处于显式事务中(BEGIN 未 COMMIT),一律走主库。更隐蔽的是隐式事务:某些 ORM(如 Django 的 select_for_update())或 JDBC 的 setAutoCommit(false),会悄悄开启事务,导致后续 SELECT 被误判为可读从库。
实操建议:
- 用 SQL 注释标记路由意图,例如
/* FORCE_MASTER */ SELECT * FROM user,中间件识别后强制走主 - 避免在同一个事务里混用主/从库连接,JDBC 连接池(如 HikariCP)不支持跨库事务
- 应用层不要缓存从库查询结果太久,否则可能放大复制延迟带来的数据陈旧问题
应用层直连方案要小心事务传播
跳过中间件、由应用自己选库(如 Spring 的 AbstractRoutingDataSource),灵活性高但责任全在业务代码。典型坑是:Service 方法加了 @Transactional,但内部调用了两个 DAO —— 一个写主库,一个读从库,最终抛出 Cannot change transaction isolation level after connection has been obtained。根本原因是 Spring 默认复用同一连接,而主从库连接参数(如隔离级别、时区)不同,无法兼容。
绕过方式:
- 用
@Transactional(propagation = Propagation.NOT_SUPPORTED)临时退出事务,再调用只读 DAO - 把读操作抽成独立无事务方法,并确保调用链不被外层事务传播影响
- 主库连接池和从库连接池必须完全隔离,不能共用
DataSource实例
从库负载不均和慢查询会反向拖垮主库
很多人只关注“读请求分走了”,却忽略从库本身也是单点瓶颈。比如某个从库因磁盘 IO 差、配置低、或被慢查询占满连接,导致复制线程卡住(Slave_SQL_Running_State: Reading event from the relay log),主库 binlog 积压,最终触发主库磁盘爆满。另一个问题是读请求没做分片,所有报表类大查询都打到同一台从库。
必须做的检查:
- 定期用
SHOW PROCESSLIST或pg_stat_activity抓长事务和慢查询,设置long_query_time=1记录到慢日志 - 从库开启
read_only=ON(MySQL)或default_transaction_read_only=on(PostgreSQL),防误写 - 按业务类型划分从库:实时查询走高性能从库,离线分析走专用只读集群,物理隔离
实际最难的从来不是怎么配主从或写路由规则,而是厘清哪些查询真的可以容忍延迟、哪些看似只读实则依赖最新状态、以及当复制中断时,你的告警是否能在 30 秒内触达值班人。










