SpringBoot项目里用Dynamic-Datasource搞读写分离,我踩过的坑你别再踩了

张开发
2026/5/3 4:56:16 15 分钟阅读
SpringBoot项目里用Dynamic-Datasource搞读写分离,我踩过的坑你别再踩了
SpringBoot项目中Dynamic-Datasource读写分离实战避坑指南最近在重构公司的一个核心业务系统时我们决定引入读写分离来缓解数据库压力。经过技术选型最终采用了Dynamic-Datasource这个轻量级解决方案。本以为配置简单就能搞定结果在实际落地过程中踩了不少坑。今天就把这些经验教训整理出来希望能帮到正在或计划使用Dynamic-Datasource的开发者们。1. 数据源切换失效的典型场景1.1 Transactional与DS注解的冲突第一次遇到的问题是明明在方法上加了DS(slave)注解查询却总是跑到主库执行。经过排查发现当方法同时使用Transactional和DS注解时事务注解会覆盖数据源切换逻辑。// 错误示例事务注解导致数据源切换失效 Transactional DS(slave) public User getUser(Long id) { return userMapper.selectById(id); // 实际会在主库执行 }解决方案有两种使用DSTransactional替代TransactionalDSTransactional DS(slave) public User getUser(Long id) { return userMapper.selectById(id); }调整事务传播行为适用于需要严格事务控制的场景Transactional(propagation Propagation.REQUIRES_NEW) DS(slave) public User getUser(Long id) { return userMapper.selectById(id); }1.2 嵌套调用时的数据源切换另一个常见问题是Service方法间的嵌套调用导致数据源切换失效。例如DS(master) Service public class OrderServiceImpl implements OrderService { Autowired private UserService userService; public void createOrder(Order order) { // 这里期望使用master数据源 orderMapper.insert(order); // 内部调用userService方法 User user userService.getUser(order.getUserId()); // 数据源不会切换到slave } } DS(slave) Service public class UserServiceImpl implements UserService { public User getUser(Long id) { return userMapper.selectById(id); // 实际仍在master执行 } }这是因为Dynamic-Datasource默认基于AOP实现而Spring的代理机制在内部调用时不会再次触发切面。解决方案避免直接内部调用通过Autowired注入自身代理对象使用ApplicationContext获取BeanAutowired private ApplicationContext applicationContext; public void createOrder(Order order) { orderMapper.insert(order); UserService userService applicationContext.getBean(UserService.class); User user userService.getUser(order.getUserId()); }2. 事务管理的正确姿势2.1 多数据源事务的局限性Dynamic-Datasource虽然支持多数据源但无法提供真正的分布式事务。当需要跨数据源保持一致性时需要考虑以下方案方案适用场景实现复杂度一致性保障最终一致性容忍短暂不一致低最终一致Seata集成强一致性要求高强一致本地消息表异步场景中最终一致2.2 DSTransactional的使用陷阱DSTransactional虽然解决了多数据源切换问题但有几个需要注意的点异常处理受限无法像Transactional那样捕获异常后手动回滚// 错误示例手动回滚不生效 DSTransactional public void updateUser(User user) { try { userMapper.updateById(user); } catch (Exception e) { // 这里的处理无效事务已经回滚 log.error(更新失败, e); } }方法级注解才有效类级别的DSTransactional可能不生效性能影响每个DSTransactional方法都会新建连接高频调用时需注意连接池配置3. 动态数据源管理的最佳实践3.1 运行时增减数据源的时机项目中我们遇到过这样的场景需要根据租户动态添加数据源。初始实现是在每次请求时检查结果导致了性能问题// 低效实现每次请求都检查数据源是否存在 DS(#header.tenant) public ListProduct getProducts() { if(!dataSourceExists(tenant)) { addDataSource(tenant); // 频繁的同步操作 } return productMapper.selectList(); }优化后的方案启动时预加载已知的租户数据源在应用启动时初始化异步添加新租户首次访问时异步添加数据源Async public void asyncAddDataSource(String tenant) { // 添加数据源实现 }3.2 数据源配置的优化建议根据压测经验推荐以下连接池配置以Druid为例spring: datasource: druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false特别提醒读写分离场景下从库的连接池可以适当调大因为读请求通常比写请求频繁。4. 监控与故障排查技巧4.1 日志配置建议为了便于排查问题建议增加以下日志配置# 显示实际使用的数据源 logging.level.com.baomidou.dynamic.datasourceDEBUG # 显示SQL执行情况 logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManagerDEBUG4.2 常见问题速查表现象可能原因解决方案数据源切换不生效1. 注解位置错误2. 事务冲突3. 嵌套调用检查注解优先级使用DSTransactional事务不回滚1. 异常被捕获2. 异常类型未配置检查Transactional的rollbackFor性能下降1. 连接池配置不当2. 频繁创建数据源优化连接池参数预加载数据源数据不一致1. 主从延迟2. 跨库事务考虑强制读主库或使用Seata4.3 强制读主库的应急方案在某些对实时性要求高的场景可以提供强制走主库的查询方式public User getUser(Long id, boolean masterOnly) { if(masterOnly) { return masterUserMapper.selectById(id); } return slaveUserMapper.selectById(id); }或者在注解中使用SpEL表达式DS(#masterOnly ? master : slave) public User getUser(Long id, boolean masterOnly)5. 进阶多租户与读写分离的结合在实际项目中我们经常需要同时处理多租户和读写分离的需求。这时可以采用分组数据源的方式spring: datasource: dynamic: primary: master strict: true datasource: master_tenant1: url: jdbc:mysql://master1/db1 group: master slave1_tenant1: url: jdbc:mysql://slave1/db1 group: slave master_tenant2: url: jdbc:mysql://master2/db2 group: master slave1_tenant2: url: jdbc:mysql://slave2/db2 group: slave使用时通过注解指定租户和数据源组DS(slave_#tenant) public ListProduct getProducts(String tenant) { // ... }这种方案需要注意租户标识的传递建议使用ThreadLocal或请求头连接池的合理配置避免租户过多导致连接数暴涨动态增减租户时的资源清理

更多文章