MySQL 读写分离代码实现思路¶
一、架构¶
主库负责写,从库同步主库 Binlog,应用读走从库。
二、路由策略¶
核心问题:一个 SQL 发主库还是从库?
1. 静态规则¶
INSERT/UPDATE/DELETE→ 主库。SELECT→ 从库。
2. 动态规则¶
- 事务内的读走主库(避免主从延迟导致读不到刚写的数据)。
- 强制走主库:
/*+ master */ SELECT ...。 - 慢 SQL 走主库,避免从库延迟。
三、实现方式¶
方案 1:Spring AbstractRoutingDataSource¶
Spring 提供了抽象类,可以根据 key 动态切换数据源:
public class MasterSlaveDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContext.get(); // "master" / "slave"
}
}
线程上下文保存当前路由 key:
public class DataSourceContext {
private static final ThreadLocal<String> CTX = new ThreadLocal<>();
public static void master() { CTX.set("master"); }
public static void slave() { CTX.set("slave"); }
public static String get() { return CTX.get(); }
public static void clear() { CTX.remove(); }
}
用 AOP 在 Service 方法上切换:
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(transactional)")
public void before(Transactional transactional) {
DataSourceContext.master();
}
@After("@annotation(transactional)")
public void after() {
DataSourceContext.clear();
}
}
方案 2:ShardingSphere-JDBC¶
成熟中间件,配置即生效:
dataSources:
master: ...
slave1: ...
rules:
- !READWRITE_SPLITTING
dataSources:
write_ds: master
read_ds: [slave1]
四、主从延迟问题¶
主从同步是异步的,写主库后立刻读从库可能读不到。
解决方法¶
- 强制读主:写操作后的短暂时间内读主库。
- 中间件判断:从库同步位点落后于主库多少时,读走主。
- 半同步复制:主库等至少一个从库收到 Binlog 才返回。
- 业务层容忍:大部分场景最终一致即可。
五、注意事项¶
- 事务必须走主库:否则写一半切到从库读不到。
DataSourceContext.clear()必须在 finally 里,否则线程复用导致路由错乱。- 连接池隔离:主从用不同连接池,避免互相影响。
- 多从库负载均衡:简单轮询、权重、一致性 hash。
面试加分
- 读写分离是应用层路由,不是 MySQL 自带的。
- ShardingSphere / MyCat / Vitess 是常用中间件。