723 字
约 2 分钟
6
S08-A02 · 多数据源与动态切换
适用读者:要做读写分离或分库路由的人。
前置:S08-B01、S07-J01(事务)。
覆盖旧文:2386(530) 515(36) 516(38) = 604 点。
覆盖扩展:E-S08-007(AbstractRoutingDataSource与事务绑定)。
上一篇:S08-A01 · 分库分表:为什么、怎么分、带来什么
下一篇:S08-A03 · ORM 选型。
1. 一句话结论
动态数据源靠 AbstractRoutingDataSource + ThreadLocal 决定"这次用哪个库"。关键陷阱:数据源在事务开始时就被绑定,事务内切换无效。
determineCurrentLookupKey() → ThreadLocal 里的 key → 对应 DataSource
2. 标准实现三段式
public class RoutingDataSource extends AbstractRoutingDataSource {
protected Object determineCurrentLookupKey() { return DsContext.get(); }
}
public class DsContext {
private static final ThreadLocal<String> DS = new ThreadLocal<>();
public static void set(String k) { DS.set(k); }
public static String get() { return DS.get(); }
public static void clear() { DS.remove(); } // 必须清理
}
注册:@Bean DataSource 里把多个真实数据源放进 targetDataSources。
3. 与事务的冲突(E-S08-007)
| 事实 | 后果 |
|---|---|
| 事务开始时已确定连接 | 事务内 DsContext.set() 不生效 |
| 连接来自"事务绑定的数据源" | 跨库写需要分布式事务 |
ThreadLocal 遇线程池 |
不清理会串库(S05-B04 同源) |
实测同源证据(S07-J01):@Transactional 只在代理入口生效——数据源选择也发生在那个时刻。
判据:"切换发生在事务之前还是之内?" 之内 → 无效。
4. 读写分离的四种做法
| 做法 | 说明 |
|---|---|
| 应用层路由(本方案) | 代码显式切只读库 |
| 中间件(ShardingSphere-Proxy) | 对应用透明 |
| 驱动层(如 MySQL Router) | 透明但配置复杂 |
| 强制主库 | 写后立即读(防主从延迟) |
主从延迟是硬事实:写完立刻读从库可能读不到——关键路径要"写后读主库"。
5. 五个误区
| 误区 | 真相 |
|---|---|
| "事务内能切数据源" | 连接已绑定 |
| "ThreadLocal 不用清理" | 线程池会串库 |
| "读写分离后写也走从库" | 写必须走主库 |
| "主从延迟可以忽略" | 秒级延迟很常见 |
| "多数据源能替代分库分表" | 解决读压力,不解决单表容量 |
6. 自检
AbstractRoutingDataSource靠什么决定数据源?- 为什么事务内切换无效?
- ThreadLocal 不清理会怎样?
- 读写分离后"写后立即读"怎么保证?
- 多数据源与分库分表的边界?
7. 下一篇
S08-A03 · ORM 选型:MyBatis vs JPA/Hibernate
多数据源讲完,最后一篇架构文:手写 SQL 与全自动 ORM 怎么选。
本篇属于系列《S08 · MyBatis 持久层》。旧文来源:A-2386/515/516;扩展点 E-S08-007。
S08-A02 · 多数据源与动态切换
https://www.clxhxhhr.top/posts/5294/ 评论
0 条
还没有评论,先写一条吧。