MySQL主从复制是读写分离的前提,需确保Seconds_Behind_Master≈0且IO/SQL线程正常;MyBatis Plus需借助DynamicDataSource或ShardingSphere-JDBC实现路由,推荐@DS注解;事务内强制走主库,跨库JOIN和分布式事务需最终一致性方案。

MySQL 主从复制是读写分离的底层前提
没有主从同步,读写分离就是空中楼阁。应用层把读请求发到从库,但数据还没同步过去,查到的就是过期结果。所以必须先确保 SHOW SLAVE STATUS\G 中 Seconds_Behind_Master 稳定为 0 或极低值(比如 1),且 Slave_IO_Running 和 Slave_SQL_Running 都是 Yes。
常见错误现象:Slave_SQL_Running: No,通常是因为主库执行了 DROP TABLE 或 CREATE TEMPORARY TABLE 这类不被从库兼容的操作;或者从库手动改了数据导致主键冲突。
- 主库
binlog_format必须设为ROW,MIXED在某些场景下会丢变更 - 从库建议开启
read_only=1,防止应用误写入(但 root 用户仍可写,需额外限制) - 避免在从库执行
STOP SLAVE后长时间不启,容易因 binlog 清理导致同步断裂
MyBatis Plus 多数据源下怎么路由读/写
MyBatis Plus 本身不带读写分离能力,得靠 DynamicDataSource 或 ShardingSphere-JDBC 这类中间件做路由。最轻量的是用 com.baomidou.dynamic.datasource.DynamicRoutingDataSource,它根据方法名前缀或注解决定走哪个数据源。
使用场景:服务里一个 UserService,getUserById 走从库,updateUser 走主库——不能靠 SQL 类型判断(比如 SELECT 就走从库),因为有些业务逻辑里 SELECT ... FOR UPDATE 必须走主库。
- 推荐用
@DS("slave")注解标在 service 方法上,比正则匹配更可控 - 事务方法内如果嵌套了
@DS("slave"),会被忽略,整个事务强制走主库——这是正确行为,不是 bug - 注意
DataSourcebean 初始化顺序:主数据源必须叫master,否则DynamicRoutingDataSource找不到默认源
ShardingSphere-JDBC 的 readwrite-splitting 配置要点
ShardingSphere-JDBC 是目前最成熟的读写分离嵌入式方案,但配置稍复杂。核心是定义 write-data-source-name 和 read-data-source-names,它不会自动发现从库延迟,得靠 load-balancer-name 控制怎么选从库。
性能影响:默认负载均衡策略是 ROUND_ROBIN,但如果某个从库延迟高,请求分过去就拖慢整体响应。可以换 RANDOM 或自定义实现 SQLHint 强制走指定从库。
-
props下加sql-show: true可看实际路由日志,确认读请求是否真发到了从库 - 不要把主库地址也塞进
read-data-source-names,否则读压力会反向打到主库 - 如果用了 HikariCP,每个物理数据源都要单独配连接池,别共用一个
HikariDataSource实例
应用层直连多数据库时,事务和跨库 JOIN 怎么办
一旦拆成主+从多个物理连接,本地事务(@Transactional)就只对当前数据源生效。如果一个方法里既更新主库又查从库,主库失败回滚了,从库那条查询结果已经返回给前端——这不是一致性问题,而是设计误用。
跨库 JOIN 更麻烦:MySQL 不支持主从库之间直接 JOIN,ShardingSphere 也不支持跨写/读数据源的关联查询。
- 事务边界必须收缩到单数据源内,涉及主从协同的逻辑,用最终一致性 + 补偿任务代替
- 需要“读己写”(比如刚下单立刻查订单列表)的场景,读请求必须走主库,别为了读写分离牺牲正确性
- 报表类需求要 JOIN 多张表,优先考虑把汇总结果定时同步到单独的分析库,而不是现场跨主从拉数据
真正难的从来不是配出读写分离,而是判断哪条查询该走从库、哪条必须走主库——这个决策藏在业务语义里,没法靠工具自动猜准。

















