
1. 项目概述在真实的业务开发里一个SpringBoot应用只连一个数据库的场景说实话越来越少了。我最近接手的一个老系统重构项目就遇到了典型的“数据孤岛”问题用户主数据在MySQL的user_center库订单交易流水在另一个MySQL实例的order_db库而一些运营报表数据又躺在单独的PostgreSQL里。如果还硬着头皮用一个数据源写一堆跨库join的SQL或者手动在代码里切换连接那维护起来简直就是一场灾难代码会变得又臭又长性能也堪忧。所以“多数据源”配置从一个“炫技”的亮点变成了中大型项目里一个必须掌握的基础设施能力。简单来说SpringBoot多数据源的核心目标就一个让应用能够透明、灵活地同时连接并操作多个不同的数据库。这里的“不同”可能是完全不同的数据库类型如MySQL和PostgreSQL也可能是同一数据库类型下的不同实例或不同Schema。实现方式上社区里主流的有两大流派各有各的适用场景和脾气。第一种是“静态配置派”通过在application.yml里明确定义多个DataSourceBean然后用Primary和Qualifier来手动指定注入哪个。这种方式直白、可控适合数据源数量固定、切换逻辑简单的场景。第二种则是“动态路由派”通常借助像dynamic-datasource-spring-boot-starter这样的第三方组件它内置了一个基于AOP的动态数据源路由器可以根据方法上的注解如DS(“slave”)或者自定义规则在运行时动态决定使用哪个连接特别适合读写分离、分库分表这类需要频繁切换的场景。无论选哪种搞明白背后的原理知道怎么避开那些坑比如经典的“failed to configure a datasource: ‘url’ attribute”报错才能真正让多数据源为你的项目服务而不是添乱。接下来我就结合自己的踩坑经验把这“两派”武功的招式、心法以及实战中的“避坑指南”给你拆解清楚。2. 核心思路与方案选型在动手写代码之前先花点时间想清楚你的业务场景到底适合哪种模式这能省下后面至少80%的调试时间。选择不是非此即彼但有一个清晰的倾向性。2.1 静态配置派简单直接手动管理这种方式的思路非常符合Spring的传统哲学一切皆Bean。你需要在配置类里显式地创建两个或多个DataSource的Bean并为它们配置好各自的驱动、URL、用户名、密码等连接属性。然后Spring的IoC容器里就会存在多个同类型的DataSourceBean。为了让Spring在自动装配时知道该用哪个你需要用Primary注解标记其中一个作为默认数据源通常是你主要的业务库而对于其他数据源在注入时则必须使用Qualifier注解按Bean的名字来精确指定。它的优势在于绝对的控制力。每一个数据源的生命周期、属性配置都完全掌握在你手里没有黑魔法。调试的时候也一目了然Bean定义清晰。但缺点也同样明显切换繁琐。每次操作非默认数据源你都得在Service或Mapper层显式地通过Qualifier注入对应的DataSource或JdbcTemplate或者获取对应的SqlSessionFactory。这会导致业务代码和数据源配置产生耦合如果数据源多了代码里会散落着各种Qualifier维护起来心累。所以它最适合的场景是数据源数量很少比如就2个且切换模式固定。例如一个主业务库一个独立的日志库日志库只在写审计日志时使用这种边界清晰的场景用静态配置反而更清爽。2.2 动态路由派注解驱动自动切换当你的场景变得复杂比如最常见的“一主多从”读写分离或者按照业务模块分库用户一个库商品一个库静态配置就显得力不从心了。你不可能在每一个查询方法里都去手动指定用哪个从库。这时动态路由方案就派上用场了。它的核心思想是引入一个抽象层一个AbstractRoutingDataSource。这个类本身也是DataSource但它并不真正持有数据库连接而是一个“路由器”。它内部维护了一个Map映射着数据源的标识Key和真实的DataSource对象Value。同时它提供了一个determineCurrentLookupKey()方法这个方法返回的字符串就是当前操作应该使用哪个数据源的Key。如何决定这个Key呢通常的做法是利用Spring AOP在方法执行前解析方法或类上的特定注解例如DS(“slave1”)将数据源Key设置到一个线程安全的上下文如ThreadLocal中AbstractRoutingDataSource再从这里面取出Key完成路由。社区里最成熟的实现就是dynamic-datasource-spring-boot-starter它把上述流程封装得非常好你只需要引入依赖在配置文件中定义好多个数据源然后在方法上加DS注解就行了几乎零侵入。它的优势是解耦和灵活业务代码无需关心底层连接细节通过注解声明意图即可。非常适合动态性强的场景。2.3 选型决策要点怎么选我总结了一个简单的决策树数据源数量与变动频率数量固定≤3个且几乎不变选静态数量可能增加或需要动态增减选动态。切换逻辑复杂度切换规则简单固定如按Dao类固定静态勉强可用切换规则复杂按方法、按参数、按业务规则必须用动态。团队熟悉度与项目阶段如果是老项目小范围改造团队对Spring原生配置更熟可以用静态快速上线。如果是新项目且预见有多数据源需求强烈建议直接从动态方案开始长远看更省事。第三方集成如果你的项目已经大量使用了MyBatis-Plus那么用其作者提供的dynamic-datasource-spring-boot-starter会有更好的集成体验和社区支持。我个人的经验是在微服务架构下动态路由方案几乎是标配。它带来的代码整洁度和扩展性远超过其引入的微小复杂度。3. 静态配置方式详解与实操理论说完我们来真刀真枪地配置。假设我们有一个电商系统主业务库trade_dbMySQL和积分库points_dbMySQL不同实例我们就用静态配置来实现。3.1 环境准备与依赖首先创建一个标准的SpringBoot项目。在pom.xml中我们需要引入数据库驱动和连接池依赖。这里我推荐使用HikariCP它是SpringBoot 2.x后的默认连接池性能非常好。dependencies !-- SpringBoot Web Starter (根据你的项目类型选择) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- SpringBoot JDBC 支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 如果使用MyBatis还需要引入mybatis-spring-boot-starter -- !-- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.0/version /dependency -- /dependencies3.2 多数据源配置类编写这是静态配置的核心。我们不再依赖application.yml中的spring.datasource自动配置因为那个只会帮我们创建一个默认的DataSourceBean。我们需要自己写一个配置类手动创建多个Bean。import com.zaxxer.hikari.HikariDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; Configuration public class DataSourceConfig { /** * 主数据源 (trade_db) * 使用 Primary 注解表示当存在多个同类型Bean时优先注入这个 */ Primary // 关键注解 Bean(name primaryDataSource) ConfigurationProperties(prefix spring.datasource.primary) // 绑定配置前缀 public DataSource primaryDataSource() { // 这里用DataSourceBuilder创建它会自动根据classpath中的连接池创建对应的实例如Hikari return DataSourceBuilder.create().build(); } /** * 次数据源 (points_db) */ Bean(name secondaryDataSource) ConfigurationProperties(prefix spring.datasource.secondary) public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } }关键点解析Primary这个注解至关重要。Spring在自动装配时如果发现多个DataSource类型的Bean又没有明确指定通过Qualifier就会报错。Primary告诉Spring“如果纠结就选我当默认的。” 通常你的核心业务库应该被标记为Primary。ConfigurationProperties这是SpringBoot的魔法。它将application.yml中spring.datasource.primary和spring.datasource.secondary下面的所有属性url,username,password,driver-class-name,hikari配置等自动绑定到DataSourceBuilder创建的对象上。这样我们就不需要在代码里硬编码连接信息了。Bean(name “...”)给Bean起个名字方便后面用Qualifier引用。3.3 配置文件 application.yml接下来在application.yml中配置两个数据源的具体连接信息。spring: datasource: # 主数据源配置 primary: jdbc-url: jdbc:mysql://localhost:3306/trade_db?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_primary_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: connection-timeout: 30000 maximum-pool-size: 20 minimum-idle: 5 # 次数据源配置 secondary: jdbc-url: jdbc:mysql://192.168.1.100:3306/points_db?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai username: points_user password: your_secondary_password driver-class-name: com.mysql.cj.jdbc.Driver hikari: connection-timeout: 30000 maximum-pool-size: 15 minimum-idle: 3注意这里使用的是jdbc-url而不是url。在SpringBoot 2.x及以上版本如果你同时配置了url和jdbc-url且使用了HikariCP可能会因为属性覆盖问题导致配置不生效。为了清晰和避免冲突统一使用jdbc-url是更稳妥的做法。这也是很多同学遇到“failed to configure a datasource”错误的一个潜在原因。3.4 在Service或DAO层使用配置好了怎么用呢假设我们有一个OrderService需要操作主库一个PointsService需要操作积分库。import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import javax.sql.DataSource; Service public class OrderService { // 注入主数据源由于有Primary可以不用Qualifier但显式指定更清晰 Autowired Qualifier(primaryDataSource) private DataSource primaryDataSource; // 通常我们更常用JdbcTemplate private JdbcTemplate primaryJdbcTemplate; Autowired public void setPrimaryDataSource(Qualifier(primaryDataSource) DataSource dataSource) { this.primaryJdbcTemplate new JdbcTemplate(dataSource); } public void createOrder(Order order) { String sql INSERT INTO orders(...) VALUES (...); // 使用 primaryJdbcTemplate 操作主库 primaryJdbcTemplate.update(sql, ...); } } Service public class PointsService { // 注入次数据源必须使用Qualifier指定Bean名称 Autowired Qualifier(secondaryDataSource) private DataSource secondaryDataSource; private JdbcTemplate secondaryJdbcTemplate; Autowired public void setSecondaryDataSource(Qualifier(secondaryDataSource) DataSource dataSource) { this.secondaryJdbcTemplate new JdbcTemplate(dataSource); } public void addPoints(Long userId, int points) { String sql UPDATE user_points SET points points ? WHERE user_id ?; // 使用 secondaryJdbcTemplate 操作积分库 secondaryJdbcTemplate.update(sql, points, userId); } }如果你用的是MyBatis那么还需要为每个数据源配置独立的SqlSessionFactory和MapperScannerConfigurer并指定它们各自的DataSource和Mapper接口包路径过程类似但更繁琐一些。3.5 静态配置的优缺点与避坑点优点原理简单易于理解和调试。对每个数据源的控制粒度极细可以分别定制连接池参数。不依赖第三方库纯Spring原生支持。缺点代码侵入性强业务层需要感知数据源。扩展性差新增数据源需要修改配置类和所有使用到的地方。事务管理复杂如果需要跨数据源的事务分布式事务需要引入额外的框架如JTA。避坑指南“Failed to configure a DataSource: ‘url’ attribute is not specified”这是最常见的错误。根本原因是SpringBoot的自动配置DataSourceAutoConfiguration试图为你创建一个默认数据源但在你的配置里它找不到spring.datasource.url。解决方案在主启动类上排除DataSourceAutoConfiguration的自动配置SpringBootApplication(exclude {DataSourceAutoConfiguration.class})。这样SpringBoot就不会尝试自动创建数据源完全由你的配置类接管。连接池配置不生效检查是否使用了正确的属性前缀如spring.datasource.primary.hikari.*并且确保没有在代码里用setXXX()方法覆盖了配置文件的值。事务失效在静态多数据源下Spring的Transactional注解默认只对Primary标记的数据源生效。如果你在操作次数据源的方法上加了Transactional它可能不会按你期望的回滚。对于需要跨数据源事务的场景建议使用Seata等分布式事务解决方案或者将不同数据源的操作拆分成独立的事务方法。4. 动态路由方式详解与实操基于dynamic-datasource对于大多数需要灵活切换的场景我强烈推荐使用dynamic-datasource-spring-boot-starter。它极大地简化了多数据源的开发。下面我们以实现一个“一主二从”的读写分离场景为例。4.1 引入依赖首先在pom.xml中添加依赖。注意选择与你的SpringBoot版本兼容的版本。dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version3.6.1/version !-- 请使用最新稳定版 -- /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency4.2 配置文件 application.yml配置变得异常简洁。你只需要在一个spring.datasource.dynamic节点下定义你的所有数据源。spring: datasource: dynamic: primary: master # 设置默认的数据源主库默认值为master strict: false # 是否启用严格模式。严格模式下未匹配到指定数据源会抛异常非严格模式下则使用默认数据源 datasource: # 主库 master master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/master_db?useSSLfalseserverTimezoneAsia/Shanghai username: root password: master_password # 从库 slave_1 slave_1: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.1.101:3306/slave_db?useSSLfalseserverTimezoneAsia/Shanghai username: slave_user password: slave_password # 从库 slave_2 slave_2: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.1.102:3306/slave_db?useSSLfalseserverTimezoneAsia/Shanghai username: slave_user password: slave_password # 以下是可选的全局配置如连接池参数 hikari: connection-timeout: 30000 maximum-pool-size: 15 minimum-idle: 54.3 使用 DS 注解切换数据源这是最核心、最优雅的部分。你不需要在代码里注入不同的JdbcTemplate或DataSource只需要在方法或类上添加一个DS注解。import com.baomidou.dynamic.datasource.annotation.DS; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; Service public class UserService { // 这里注入的是动态数据源路由包装后的 JdbcTemplate Autowired private JdbcTemplate jdbcTemplate; // 写操作使用主库。如果类上没注解方法上也没注解则使用默认的primary数据源master DS(master) // 显式指定主库清晰明确 public void createUser(User user) { String sql INSERT INTO users(...) VALUES (...); jdbcTemplate.update(sql, ...); } // 读操作1使用从库slave_1。通过注解轻松实现读写分离 DS(slave_1) public User getUserByIdFromSlave1(Long id) { String sql SELECT * FROM users WHERE id ?; return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(User.class), id); } // 读操作2使用从库slave_2。可以配合负载均衡策略轮询使用从库 DS(slave_2) public ListUser getAllUsersFromSlave2() { String sql SELECT * FROM users; return jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class)); } // 不指定DS则使用默认数据源master public User getDefaultUser(Long id) { // 这个方法会使用 master 数据源 String sql SELECT * FROM users WHERE id ?; return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(User.class), id); } }注解的作用域DS可以标注在方法上优先级最高。也可以标注在类上那么这个类里所有方法默认都使用这个数据源。如果方法和类上都有注解方法上的会覆盖类上的。如果都没有则使用spring.datasource.dynamic.primary指定的默认数据源。4.4 进阶用法与原理浅析dynamic-datasource的强大不止于此。它底层依赖于AbstractRoutingDataSource和Spring AOP。当你调用一个被DS注解的方法时会发生以下几步AOP拦截器DsProcessor会先于方法执行。拦截器解析DS注解的值得到数据源Key如“slave_1”。将这个Key设置到一个ThreadLocal变量中DynamicDataSourceContextHolder。方法内部的数据库操作通过JdbcTemplate、MyBatis Mapper等需要获取连接时会调用AbstractRoutingDataSource的determineCurrentLookupKey()方法。该方法从ThreadLocal中取出Key然后从预先注册好的数据源Map中找到对应的真实DataSource获取连接。方法执行完毕后AOP后置处理会清理ThreadLocal中的Key避免内存泄漏和上下文污染。基于这个原理你可以实现更复杂的路由策略SPEL表达式DS(“#session.tenantId”)可以根据会话中的租户ID动态路由到不同的数据库多租户场景。自定义策略实现DynamicDataSourceStrategy接口可以定义负载均衡策略比如随机、轮询、负载最低等然后在DS注解中不指定具体从库名而是指定一个分组名如DS(“slave”)框架会自动根据策略从slave_1和slave_2中选择一个。4.5 动态路由的优缺点与避坑点优点接近零侵入业务代码只需添加注解与数据源解耦。灵活强大支持注解、SPEL、自定义策略等多种路由方式。功能丰富内置读写分离、多租户等常见场景支持社区活跃。与MyBatis-Plus集成无缝如果是MyBatis-Plus用户体验更佳。缺点引入第三方依赖需要管理依赖版本和兼容性。理解成本稍高需要对其AOP和ThreadLocal的机制有一定了解才能更好地排查问题。事务上下文管理同样需要注意事务边界内的数据源切换问题。避坑指南事务内切换失效这是动态数据源最常见的问题。Transactional和DS都是基于Spring AOP实现的。如果它们在同一个方法上并且Transactional的切面先执行它就会先获取一个数据库连接此时数据源Key可能还没被DS的切面设置导致后续操作始终使用这个连接DS注解失效。解决方案确保DS注解的切面优先级高于Transactional。dynamic-datasourcestarter已经处理了这一点但如果你有自定义切面需要注意顺序。更稳妥的做法是将DS注解放在Service方法上而将Transactional注解放在调用Service方法的上层如Controller或另一个无DS注解的Service方法。ThreadLocal污染如果在方法内启用了新线程如Async异步方法新线程无法继承父线程的ThreadLocal数据源Key会导致切换到默认数据源。解决方案需要手动传递数据源Key或使用支持ThreadLocal传递的线程池包装器。配置错误确保spring.datasource.dynamic.datasource下的每个数据源key如master,slave_1与DS注解中使用的字符串完全一致包括大小写。连接池配置虽然可以在spring.datasource.dynamic.hikari下配置全局参数但如果你需要对某个特定数据源如主库进行特殊配置可以这样写spring: datasource: dynamic: datasource: master: url: ... hikari: # 主库独有配置 maximum-pool-size: 30 connection-timeout: 100005. 事务管理与跨数据源问题多数据源环境下事务管理是一个必须严肃对待的问题。Spring的Transactional注解及其背后的事务管理器PlatformTransactionManager默认是针对单个数据源工作的。5.1 单数据源事务在静态配置中如果你为每个数据源都配置了独立的DataSourceTransactionManager那么Transactional注解会默认使用Primary标记的那个。对于非主数据源的操作你需要通过Transactional注解的transactionManager属性来指定对应的事务管理器Bean名称。在动态路由dynamic-datasource中它提供了一个统一的DynamicDataSourceTransactionManager能够根据当前线程上下文中DS注解指定的数据源自动选择对应的事务管理器底层是管理对应的真实数据源连接。所以在同一个Service方法内部如果所有数据库操作都通过同一个DS注解路由到同一个物理数据库那么事务是正常工作的。5.2 跨数据源事务分布式事务的挑战真正的难题在于跨数据源事务即一个业务方法需要同时更新主库和积分库并且要求要么都成功要么都回滚。例如用户下单成功后既要更新订单库又要增加用户积分。这是一个典型的分布式事务场景。方案一最大努力通知最终一致性这是互联网高并发场景下最常用的折中方案。先执行核心操作如创建订单并提交本地事务。然后通过消息队列如RocketMQ、Kafka或定时任务异步地去执行积分增加操作。如果积分增加失败则通过重试机制不断尝试直到成功。这保证了最终数据是一致的但中间可能有短暂的不一致状态。适用场景对强一致性要求不高允许短暂延迟的最终一致性业务。方案二使用分布式事务框架如果需要强一致性可以引入Seata、Atomikos等分布式事务解决方案。以Seata为例它提供了AT、TCC等模式。你需要部署Seata服务端TC并在每个微服务或每个数据源所在的应用中集成Seata客户端。通过一个全局事务IDXID将多个本地事务关联起来由Seata来协调它们的提交和回滚。优点保证了强一致性。缺点性能有损耗架构复杂度高需要额外维护Seata服务。方案三业务设计规避这是最高明的做法。重新审视业务看能否通过设计避免跨库更新。比如能否将积分变更记录先写入订单库的本地表然后通过Binlog监听同步到积分库或者能否将用户和积分数据放在同一个数据库的不同的Schema中原则能不用分布式事务就尽量不用。5.3 实战建议评估业务容忍度首先明确你的业务对一致性的要求是“强一致”还是“最终一致”。大部分互联网业务如扣库存后发消息通知、更新订单状态后送积分都可以接受最终一致性。优先使用本地事务确保每个Service方法内部通过DS注解只操作一个物理数据库。跨库操作拆分成多个方法并通过上层逻辑或消息来协调。异步解耦对于跨库写操作优先考虑使用消息队列异步化。订单创建成功后发一条“积分增加”消息到MQ由专门的积分服务消费处理。这样订单库的事务很快提交用户体验好系统吞吐量高。慎用分布式事务框架除非是金融、交易核心等对资金强一致性有绝对要求的场景否则不要轻易引入完整的分布式事务框架它们带来的运维和性能成本可能超出你的预期。6. 性能调优与监控多数据源意味着更多的数据库连接。如果配置不当很容易导致连接池耗尽、数据库压力过大。6.1 连接池参数调优无论是静态还是动态配置连接池如HikariCP的参数都至关重要。以下是一些关键参数的经验值需要根据实际压力测试调整参数含义建议值参考说明maximumPoolSize连接池最大连接数(核心数 * 2) 磁盘数公式仅供参考。对于IO密集型数据库操作应用可以稍大如20-50。切忌盲目设大会拖垮数据库。minimumIdle连接池最小空闲连接数小于等于maximumPoolSize保持一定空闲连接快速响应请求。生产环境通常设为5-10。connectionTimeout获取连接超时时间30000 (30秒)应用程序从池中获取连接的最大等待时间。超时则抛异常。idleTimeout连接空闲超时时间600000 (10分钟)一个连接空闲多久后被释放。建议设置避免长期空闲连接。maxLifetime连接最大生命周期1800000 (30分钟)一个连接在池中的最长存活时间。数据库端可能有连接超时设置如wait_timeout此值应略小于数据库超时时间。validationTimeout连接验证超时时间5000 (5秒)验证连接有效性的超时时间。配置示例 (application.yml):spring: datasource: dynamic: datasource: master: url: ... hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 validation-timeout: 5000 connection-test-query: SELECT 1 # MySQL建议用于连接健康检查6.2 监控与诊断多数据源运行起来后监控是必不可少的。连接池监控HikariCP提供了JMX监控。你可以通过/actuator/metrics/hikaricp.connections.*端点需要SpringBoot Actuator查看活跃、空闲、等待的连接数。如果hikaricp.connections.pending等待连接数持续很高说明连接池大小可能不足或存在连接泄漏。慢SQL监控每个数据源都应开启慢查询日志。在MySQL中设置long_query_time并定期分析慢日志。可以使用Alibaba的Druid连接池替代HikariCP它内置了强大的SQL监控和防火墙功能。应用层监控在关键的业务方法上通过AOP或手动埋点记录每个数据源的操作耗时、次数。这能帮你发现哪个数据源是瓶颈或者哪个业务的数据库访问模式有问题。使用SpringBoot Admin这是一个很好的可视化监控工具可以集中查看多个SpringBoot应用的健康状态、指标和日志方便你统一监控多数据源应用。6.3 一个常见的性能陷阱连接泄漏这是多数据源环境下尤其要注意的问题。表现是运行一段时间后应用无法获取数据库连接日志报Connection is not available, request timed out after ...。排查步骤检查连接池配置确认maxLifetime小于数据库的wait_timeout。检查代码是否在所有数据库操作后都正确关闭了Connection、Statement、ResultSet推荐使用try-with-resources语法或确保在finally块中关闭。检查事务是否有长时间未提交的事务Transactional方法执行时间过长长事务会长时间占用连接。使用诊断工具启用HikariCP的leakDetectionThreshold参数例如设为60000单位毫秒它会打印出疑似泄漏连接的堆栈跟踪帮你快速定位问题代码。7. 生产环境部署与最佳实践将多数据源应用部署到生产环境除了代码正确还需要在部署和运维层面做好准备。7.1 配置分离与加密数据库密码等敏感信息绝不能硬编码在application.yml中提交到代码仓库。使用配置中心将application.yml中spring.datasource部分的配置抽离出来放到Apollo、Nacos等配置中心。应用启动时从配置中心拉取。密码加密如果配置必须放在文件中应对密码进行加密。SpringBoot支持使用Jasypt进行属性加密。在配置文件中存储加密后的密文在启动时通过环境变量传入密钥进行解密。环境区分使用application-{profile}.yml如application-prod.yml来管理不同环境开发、测试、生产的配置通过spring.profiles.active激活。7.2 高可用与故障转移对于动态路由的读写分离场景从库Slave的高可用很重要。健康检查dynamic-datasource支持为数据源配置健康检查。确保从库宕机时能自动将其从负载均衡池中剔除。多从库负载均衡如前所述利用DS(“slave”)配合轮询、随机等负载均衡策略避免流量打挂单个从库。主库故障转移主库Master的高可用通常依赖于数据库层面的主从复制与VIP切换如MHA、Orchestrator应用层面需要配合实现重连机制或故障感知。更现代的架构会使用数据库中间件如MyCat、ShardingSphere-Proxy或云数据库服务来屏蔽底层细节。7.3 版本升级与回滚升级涉及多数据源的依赖如dynamic-datasource版本或SpringBoot版本时充分测试在测试环境模拟生产流量重点测试数据源切换、事务、连接池行为。阅读变更日志仔细阅读新版本dynamic-datasource的Release Notes看是否有不兼容的配置项变更。灰度发布如果可能先对非核心业务或部分实例进行升级观察一段时间无误后再全量升级。准备回滚方案确保能快速回滚到旧版本。这意味着数据库Schema的变更也要考虑向前兼容。7.4 日志与排查为多数据源应用配置清晰的日志便于线上排查问题。开启SQL日志在开发测试环境可以配置mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl来打印SQL语句但生产环境不要开启性能损耗大且不安全。标记数据源在日志框架如Logback/SLF4J的Pattern中通过MDCMapped Diagnostic Context将当前线程使用的数据源Key可以从DynamicDataSourceContextHolder获取打印出来。这样每条日志都能知道对应哪个数据库操作排查问题时一目了然。关键操作审计对于重要的写操作尤其是跨数据源协调的操作记录操作流水到独立的审计日志表或Elasticsearch中包括操作人、时间、数据源、影响行数等便于事后追溯和对账。从我个人的经验来看多数据源从“配得通”到“用得好、管得稳”中间隔着大量的细节和实战经验。它不仅仅是加几个配置和注解更涉及到连接池调优、事务边界设计、监控告警等一系列工程实践。希望这篇从原理到实操、从配置到生产的详细拆解能帮你避开我当年踩过的那些坑真正驾驭好多数据源这把利器。