mysql读写分离必须保证事务内读操作走主库,因中间件不感知事务上下文;select for update等加锁语句及强一致性读依赖gtid+row复制,应用层须控制连接路由与事务边界。

MySQL 读写分离不是一种独立功能,而是基于主从复制构建的流量分发策略:写请求(INSERT/UPDATE/DELETE)只打向主库,读请求(SELECT)路由到一个或多个从库。它本身不解决数据同步问题,只解决请求调度问题。
为什么不能靠应用层硬编码实现读写分离
直接在代码里判断 SELECT 就连从库、其他语句就连主库,看似简单,但实际踩坑极多:
- 事务内混用主从连接会导致一致性错误——比如
BEGIN后先INSERT再SELECT,若SELECT走了从库,可能查不到刚写的记录(因复制延迟) - 从库挂掉时,代码无法自动降级或切换,请求直接失败
- 新增从库、调整权重、切换主从后,所有业务代码都要改连接地址和逻辑
- 无法识别隐式读操作(如触发器、外键检查、
SELECT ... FOR UPDATE)
主流实现方式:中间件 vs JDBC 层代理
生产环境基本不手写路由逻辑,而是依赖成熟组件:
-
ShardingSphere-JDBC(推荐):以 Jar 包形式嵌入应用,无需额外部署服务;支持事务内强制走主库、负载均衡策略(轮询/随机/权重)、延迟阈值自动剔除慢从库 -
MySQL Router:官方轻量代理,配置简单,但功能较基础,不支持 SQL 解析和复杂路由规则 -
MyCat:独立部署的代理层,兼容 MySQL 协议,适合已有应用不想改代码的场景;但运维成本高,版本迭代慢,对复杂事务支持有限
示例(ShardingSphere-JDBC 配置片段):
spring.shardingsphere.rules[0].type=READWRITE_SPLITTING spring.shardingsphere.rules[0].load-balancer-name=ROUND_ROBIN spring.shardingsphere.rules[0].data-sources.write-data-source-name=ds_master spring.shardingsphere.rules[0].data-sources.read-data-source-names=ds_slave_1,ds_slave_2
必须配合主从复制,且要注意复制延迟
读写分离的前提是主从数据能“准实时”同步,否则读到旧数据就是业务 bug。关键点:
- 主库必须开启
binlog_format=ROW,避免语句级复制导致的不一致 - 从库建议启用
relay_log_recovery=ON,崩溃重启后自动修复中继日志断点 - 监控
Seconds_Behind_Master,超过阈值(如 5 秒)应自动将该从库下线,而非等待超时再报错 - 对强一致性要求高的查询(如订单详情页),应显式走主库,
/*+ FORCE_MASTER */ SELECT ...这类 hint 在 ShardingSphere 中可用
最容易被忽略的细节:事务与连接生命周期
很多故障源于没理清“一个事务是否跨库”:
- Spring 的
@Transactional默认绑定单个数据源,如果事务内发生读写混合,必须确保整个事务走主库(readwrite-splitting组件通常提供transactional-readwrite-splitting开关) - 连接池(如 HikariCP)的
isReadOnly属性会被中间件自动设置,但手动调用connection.setReadOnly(true)可能干扰路由判断 - 从库设为
read_only=ON是底线,但超级用户仍可写——务必回收REPLICATION SLAVE权限以外的写权限
复制延迟和事务边界,才是读写分离真正难调的地方,不是配完就能用。











