SpringBoot多数据源配置避坑指南
多数据源配置配起来不难难的是配完之后不出事。我见过太多项目读写分离上线后从库延迟导致数据不一致多租户切换失效串了数据事务回滚只回滚了一个库。今天不讲怎么配只讲那些让你深夜加班、线上翻车的坑。坑一事务中切换数据源切了个寂寞这是最经典的坑。你在Service方法上加了DataSource(slave)又加了Transactional结果查询还是走了主库。原因很简单Spring事务在方法开始时就已经绑定了连接事务内的数据源切换不会生效。解决方案把DataSource放在Transactional外层。如果必须在一个事务内切库用REQUIRES_NEW开新事务但要注意这会导致两个独立事务无法一起回滚。更好的做法是读写分离的从库查询不要加事务纯查询用从库写操作走主库。坑二AOP切面顺序注解被“吃掉”了你写了DataSource注解和对应的AOP切面但发现切面没执行。大概率是切面顺序问题。如果事务切面先执行数据源切面后执行切换就失效了。Spring事务默认的Order是Ordered.LOWEST_PRECEDENCE你的切面如果没指定顺序可能排在事务后面。解决方案给数据源切面加Order(Ordered.HIGHEST_PRECEDENCE)确保它在事务切面之前执行。或者干脆把数据源切换放在事务注解外层用编程式事务控制。坑三连接池各自为政数据库被打爆每个数据源都有独立的连接池默认配置往往偏大。主库配了50个连接从库也配了50个三个数据源就是150个连接。数据库最大连接数可能才200稍微一压就满了。解决方案给每个数据源单独配置HikariCP参数根据实际流量调整maximum-pool-size。读写分离场景下从库连接池可以稍大主库要保守。同时开启连接池监控观察活跃连接数别等数据库报警了才想起来调。坑四MyBatis-Plus的DS注解不生效的N种原因用dynamic-datasource-spring-boot-starter时DS(slave)不生效常见原因有注解加在Controller上而不是Service上方法内部调用this调用导致AOP失效类上加了DS但方法上又加了Transactional事务优先级更高。解决方案DS尽量加在Service方法上避免内部调用。如果必须内部调用注入自身Bean或使用AopContext。事务和DS同时存在时确保DS在事务外层。坑五多数据源事务回滚只回了一个库默认的DataSourceTransactionManager只能管理一个数据源。你在主库插入订单从库更新库存主库回滚了从库却没回滚。这不是bug是设计如此。解决方案如果业务必须强一致引入JTA或Seata但复杂度极高。更实际的做法是尽量避免跨库事务用最终一致性替代。比如订单和库存通过消息队列异步同步保证最终一致。坑六ThreadLocal没清理线程池串数据DataSourceContextHolder用ThreadLocal存数据源key如果请求结束后没调用clear()线程被复用时会带着上一个请求的数据源标识导致数据串库。这个问题在Tomcat线程池下尤其隐蔽。解决方案在AOP的finally块中一定要调用DataSourceContextHolder.clear()确保每次请求后清理干净。坑七默认数据源没设置启动就报错AbstractRoutingDataSource必须设置defaultTargetDataSource否则当key找不到对应数据源时会抛异常。很多人只设置了targetDataSources忘了默认值结果某个方法没加注解就挂了。解决方案在配置动态数据源时务必调用setDefaultTargetDataSource(masterDataSource)。总结多数据源配置配是小事稳是大事。事务边界、AOP顺序、连接池、ThreadLocal清理、跨库事务每一个坑都可能让你线上翻车。记住一条原则能单库解决就别拆真要拆先把事务和切换边界想清楚。代码写之前多问一句“这个操作在哪个库、有没有事务、切了之后会不会失效”能省下无数个深夜排查的时间。