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. 自检

  1. AbstractRoutingDataSource 靠什么决定数据源?
  2. 为什么事务内切换无效?
  3. ThreadLocal 不清理会怎样?
  4. 读写分离后"写后立即读"怎么保证?
  5. 多数据源与分库分表的边界?

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/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。