最近在做一个国产化适配的项目,正好踩到了“若依微服务 + MySQL + 达梦双数据源”这个组合。需求其实很常见:老系统数据还在 MySQL,新系统要求兼容国产数据库,于是要在若依微服务框架里同时连 MySQL 和 DM 数据库,做一套统一接口和查询能力。这个标题所涉及的“若依微服务”“MySQL”“DM”“多数据源”就是我接下来要展开的核心内容。文章适合两类人看:一类是刚接手若依微服务但没搞过多数据源的,另一类是已经在用若依、准备接达梦但不知道从哪下手的。两种方案我都实际跑通了,先把思路和完整配置写下来,照着抄基本能落地。
1. 需求背景与整体方案选型
1.1 哪些业务场景会逼着你上多数据源
多数据源不是炫技,大多数时候是被业务逼出来的。我遇到的场景是政务项目国产化改造,历史档案存在达梦库里,实时业务数据仍在 MySQL 里,上层门户页面要把两边的数据合并展示。这就不是一个库能解决的问题。
类似的场景还有几种:
- 老系统在 MySQL,新系统要求逐步切换到达梦,过渡期必须双库共存,读接口两边都要通。
- 第三方系统或上级平台的数据放在独立数据库里,只给你只读权限,你不能把数据同步进业务主库,只能通过多数据源直连。
- 微服务按业务边界拆分成多个服务,但一些报表查询会横跨多个业务库,需要在一个服务里访问多个数据源。
- 多租户场景下,租户数据按库隔离,数据源需要动态切换。
这些需求放到若依微服务里,其实都属于“数据源路由”问题。核心思路都一样:根据请求上下文或注解,把 SQL 路由到对应的数据源。
1.2 若依微服务自带的数据源机制能做什么
若依微服务(RuoYi-Cloud)本身是带了一套多数据源雏形的。框架底层用了 Spring 的AbstractRoutingDataSource,配合自定义注解@DataSource,在 AOP 切面里做数据源切换。默认给了一个枚举DataSourceType,里面定义了MASTER和SLAVE两个类型,对应spring.datasource.druid.master和spring.datasource.druid.slave两组配置。
也就是说,若依天生支持“一个主库 + 一个从库”,主从读写分离可以直接用。但你要接一个异构数据库达梦,光靠这套默认机制就不够了,因为:
- 枚举里没有 DM 这个类型,需要自己加。
DynamicDataSource注册数据源时,只会读 master 和 slave 两个配置块,第三数据源需要改注册逻辑。- 若依这套机制没有动态添加数据源的能力,只能写死那几个数据源。
- 多数据源事务没有做支持,跨库操作容易翻车。
这不是说若依原生方案不能用,而是它更适合“固定几个库、切换逻辑简单”的场景。如果你的项目只是多一个只读达梦历史库,原生方案改造成本最低。
1.3 两条技术路线怎么选
我在这个项目里实际对比了两条路线。
路线 A:基于若依原生@DataSource机制扩展。改一个枚举,改一个数据源注册类,Nacos 配置里加一个 dm 数据源,业务代码用@DataSource(DataSourceType.DM)切换。优点是完全不引入额外依赖,改动可控,适合固定数据源场景。缺点是不支持运行时动态添加数据源,事务控制很弱,而且改的是若依框架核心代码,升级框架时要重新适配。
路线 B:引入 baomidou 的dynamic-datasource-spring-boot-starter。这是一个相对成熟的多数据源框架,支持注解切换、编程式切换、动态添加数据源、多数据源事务(配合 Seata 或本地事务)。优点是功能完整、不用动若依框架代码,缺点是多一个依赖,且要和若依默认的 Druid 自动配置解决冲突。
我的建议很明确:只是补充一个固定达梦库,走路线 A;如果你预测后面还会有第三个、第四个数据源,或者需要做多租户按库隔离,直接走路线 B。下面两条路线我都会给出完整实操,你按项目情况取舍。
2. 方案一:用若依原生 @DataSource 扩展 DM 数据源
2.1 摸清若依自带多数据源的代码位置
先定位涉及的核心类,这是改代码前最重要的一步。在若依微服务里,这几个类都集中在ruoyi-common-datasource模块:
DataSourceType:枚举,定义数据源类型。DynamicDataSourceContextHolder:用 ThreadLocal 保存当前请求使用的数据源名称。DynamicDataSource:继承AbstractRoutingDataSource,重写determineCurrentLookupKey(),返回当前数据源 key。DataSourceAspect:AOP 切面,拦截@DataSource注解,在方法执行前把数据源类型写入 ThreadLocal,执行后清理。DataSource:自定义注解。
它的运行逻辑并不复杂。请求进入业务方法时,AOP 根据注解上的数据源类型,往 ThreadLocal 里塞一个 key;MyBatis 或 JdbcTemplate 要拿数据库连接时,DynamicDataSource这个路由数据源会根据 key 从targetDataSources里挑出真正的数据源;方法结束后清空 ThreadLocal。
2.2 加枚举类型,改数据源注册逻辑
第一步是给DataSourceType增加一个枚举值。
public enum DataSourceType { MASTER, SLAVE, DM }第二步要看DynamicDataSource是怎么构建的。若依一般在配置类DruidConfig里定义DynamicDataSourceBean,大致逻辑是:
@Primary @Bean public DataSource dataSource(DataSource masterDataSource) { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(masterDataSource); return dynamicDataSource; }这里如果你只是改枚举,而不把 dm 数据源注册进targetDataSources,那么切换到 DM 时会报“找不到数据源”。所以要把 dm 数据源也加入注册逻辑,常见做法是注入一个DataSourceBean,然后在构建DynamicDataSource时把 dm 放进去。
实际项目中,我把达梦数据源单独定义为一个 Bean:
@Bean @ConfigurationProperties("spring.datasource.druid.dm") public DataSource dmDataSource() { return DruidDataSourceBuilder.create().build(); }然后在dataSource()方法里加入targetDataSources.put(DataSourceType.DM.name(), dmDataSource());。如果你的业务服务里没有单独的DruidConfig类,就把这段逻辑放到该服务自己的配置类里。
注意:若依微服务里
ruoyi-common-datasource只是在公共模块提供能力,具体数据源 Bean 一般由各业务服务的配置类实例化。改了公共模块以后,依赖它的服务都要检查是否编译通过。
2.3 在 Nacos 配置中心里加达梦数据源
若依微服务的配置文件通常托管在 Nacos 上,不是简单地改本地 yml。你需要在对应的服务配置(例如ruoyi-system-dev.yml)中添加下面内容:
spring: datasource: druid: master: url: jdbc:mysql://127.0.0.1:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 username: root password: 123456 driverClassName: com.mysql.cj.jdbc.Driver dm: url: jdbc:dm://192.168.1.10:5236/DMSERVER?schema=RUOYI username: SYSDBA password: SYSDBA driverClassName: dm.jdbc.driver.DmDriver这里有几个细节容易踩坑。第一,达梦 URL 里的schema参数要填你要访问的模式名,我建议你提前用达梦管理工具确认目标 schema 存在。第二,达梦的默认用户名SYSDBA密码在初始化时设置,实际项目的账号通常不会是默认值。第三,driverClassName一定写dm.jdbc.driver.DmDriver,不要写错成com.dm.jdbc。
修改完 Nacos 配置后发布,重启服务才能生效。
2.4 用 @DataSource 切换,并认清这套方案的边界
在业务层使用很简单:
@Service public class HistoryDataService { @Autowired private DmHistoryMapper dmHistoryMapper; @DataSource(DataSourceType.DM) public List<Map<String, Object>> listFromDm() { return dmHistoryMapper.selectHistoryList(); } }加了@DataSource(DataSourceType.DM)之后,这个方法和它内部调用的 Mapper 都会走达梦库。同类的其他方法不加注解就继续走默认 master。
需要注意,这套机制有几个边界:
- 同类内部方法调用
listFromDm(),@DataSource不会被 AOP 拦截。因为 Spring AOP 是基于代理的,内部自调用不会经过代理。要切换成功,得把方法拆到另一个 Bean 里,或者自己用AopContext.currentProxy()。 - 切面只在 Spring 管理的 Bean 上生效,Mapper 直接由 MyBatis 代理管理时,注解要放在 Service 方法上,不要只放在 Mapper 接口方法上再被普通 Service 调用。
- 多数据源事务极其有限。如果一个事务方法里既要写 MySQL 又要写达梦,这套方案做不到分布式事务,而且事务管理器通常只绑定一个数据源。
所以原生方案适合“读多写少、不需要跨库事务、数据源固定”的场景。一旦要动态切换或做跨库事务,就得换方案。
3. 方案二:接入 dynamic-datasource 统一管理多数据源
3.1 版本选择先看 Spring Boot 大版本,这是最容易翻车的点
若依微服务经典版本基于 Spring Boot 2.x,应该使用dynamic-datasource-spring-boot-starter的 3.x 版本。我实际用的是 3.6.1,配合 Spring Boot 2.7 没有问题。
如果你用的若依微服务 plus 或者自己升级到了 Spring Boot 3.x,那要用 4.x 版本。4.x 从包结构到自动配置方式变化不小,千万别把 3.x 的依赖直接塞进 Boot3 项目里,否则会报各种自动配置类不存在或循环依赖的问题。
我见过最典型的错误是:项目还是 Spring Boot 2.6,结果从网上抄了一个 dynamic-datasource 4.2.x 的依赖,启动时直接抛ClassNotFoundException或者 Bean 冲突。这里没有捷径,先确认spring-boot-starter-parent版本,再选择对应 dynamic-datasource 版本。
3.2 引入依赖,安装达梦驱动
在需要访问达梦的业务模块的pom.xml里加依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.6.1</version> </dependency> <dependency> <groupId>com.dameng</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>8.1.2.192</version> </dependency>达梦驱动有些项目不是直接放在 Maven 中央仓库里的,如果你的私服没有这个仓库,就需要把 DmJdbcDriver18.jar 手动安装到本地仓库或私服。
mvn install:install-file -Dfile=DmJdbcDriver18.jar -DgroupId=com.dameng -DartifactId=DmJdbcDriver18 -Dversion=8.1.2.192 -Dpackaging=jar这里要注意驱动版本和 JDK 版本的匹配。DmJdbcDriver18面向 JDK 1.8 及以上,大多数若依微服务项目都用 JDK 8 或 11,选这个没问题。如果项目还在用 JDK 1.6/1.7,可能需要换用旧版驱动,这在生产环境会比较少见。
3.3 处理与若依默认 Druid 的冲突
若依微服务默认引入了druid-spring-boot-starter,它的自动配置会在 Spring 容器里创建 Druid 数据源。而 dynamic-datasource 自身也会创建一组路由数据源,两者叠加会出现“一个DataSource类型的 Bean 冲突”或者启动报错。
我采用的方案是在启动类上排除自动配置:
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class) public class RuoYiStatisticsApplication { public static void main(String[] args) { SpringApplication.run(RuoYiStatisticsApplication.class, args); } }排除之后,Druid 仍然可以以普通依赖形式存在,只是不让它的自动配置来创建数据源,所有数据源统一交给 dynamic-datasource 管理。如果项目非常依赖 Druid 的监控页面,可以给每个数据源显式指定type: com.alibaba.druid.pool.DruidDataSource,并在 dynamic 配置下维护 Druid 连接池参数。如果监控需求不强,直接用 HikariCP 最简单,代码里都不用写 type。
3.4 一份能直接拷贝的 yml 配置
排除 Druid 自动配置之后,配置集中在spring.datasource.dynamic下:
spring: datasource: dynamic: primary: master strict: true druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 datasource: master: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver dm: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:dm://127.0.0.1:5236/DMSERVER?schema=RUOYI username: SYSDBA password: SYSDBA driver-class-name: dm.jdbc.driver.DmDriver两个参数要解释清楚。
primary指定默认数据源,当方法没加任何切换注解时,SQL 全部走 master。strict为true时,如果代码里指定了一个不存在的数据源 key,启动和运行时会直接报错,避免静默走默认库掩盖问题。strict为false时,找不到指定数据源就退回到主数据源。生产环境我建议用true,宁可启动时暴露错误,也不要让 SQL 悄悄走错库。
3.5 用 @DS 切换,并划好事务边界
dynamic-datasource 的切换注解是@DS,用法很直接:
@DS("dm") @Mapper public interface DmHistoryMapper { List<Map<String, Object>> selectHistoryList(@Param("code") String code); }可以在 Mapper 接口上加,也可以加在 Service 方法上。我习惯把实现对数据源要求很单一的 Mapper 独立出来,直接在 Mapper 上标注@DS("dm"),这样 Service 层不需要关心切库逻辑。如果是同一个 Service 方法里要访问两个库,就不能简单靠注解了,需要手动分段切换:
public List<Map<String, Object>> mergeData(String code) { List<Map<String, Object>> mysqlData = mysqlMapper.selectByCode(code); DynamicDataSourceContextHolder.push("dm"); try { List<Map<String, Object>> dmData = dmHistoryMapper.selectByCode(code); // 合并逻辑 } finally { DynamicDataSourceContextHolder.poll(); } }务必注意事务边界。Spring 的事务管理器在事务开启时就会从数据源拿数据库连接并把它绑定到当前事务,后续操作复用这条连接。这意味着如果外层方法加了@Transactional,事务内部再切换数据源是无效的,因为连接已经绑定到第一个数据源了。解决思路有三层:
- 尽量不跨库事务。两个库的数据操作本来就是独立事务,合并逻辑里做好失败补偿就够了。
- 如果确实需要在一个事务里操作两个库,推荐使用
@DSTransactional,它由 dynamic-datasource 提供,基于本地事务列表实现,功能有边界但比较适合轻量场景。 - 再往上就是引入 Seata 做真正的分布式事务,若依微服务集成 Seata 已有现成方案,但对大多数多数据源读场景没必要。
3.6 达梦的方言、分页和 SQL 兼容性调整
连接上达梦只是第一步,真正花时间的是 SQL 兼容。
达梦数据库语法与 Oracle 比较接近,同时也能部分兼容 MySQL,具体取决于创建实例时的兼容模式。我遇到的库是默认模式,有下面几个问题需要处理。
第一,表名大小写。达梦默认会把未加双引号的表名转成大写。MySQL 里建表时用小写表名是很常见的,导入达梦后如果仍然是敏感设置,SQL 里写小写会直接报“表或视图不存在”。稳妥的做法是:给达梦建表时明确使用大写表名,或者查询 SQL 里用双引号包裹表名。我更推荐在数据库设计阶段就统一,让达梦侧所有表名都大写,Java 层面写 SQL 时保持大写,避免运行时反复踩坑。
第二,日期函数。MySQL 里NOW()、IFNULL()在达梦默认模式下未必好用。达梦支持SYSDATE、NVL()。业务 SQL 尽量不要用同一个 mapper 文件同时服务两个库,可以按库拆分 mapper。若依的代码生成器生成的 SQL 大部分基于 MySQL,需要重写为达梦兼容写法。
第三,分页。经典若依微服务用的是 PageHelper,PageHelper 对达梦的方言支持需要通过参数指定:
pagehelper: helper-dialect: dm reasonable: false如果项目用了 MyBatis-Plus,分页插件最好显式指定数据库类型:
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.DM)); return interceptor; }不要图省事让分页插件自动识别达梦,自动识别在多数据源下容易选错方言。曾经我让插件自动判断,结果达梦分页生成了 MySQL 的LIMIT语法,达梦实例兼容模式没开,直接报语法错误。
4. 实操复现:从配置到接口全流程走一遍
4.1 场景假设与模块准备
为了让你能完整跟着操作,我设计一个最小场景。假设你有一个业务微服务ruoyi-statistics,它默认连接 MySQL 里的ry-cloud库,现在需要增加一个达梦数据源,查询历史事故档案表DM_HIS_ACCIDENT,并把查询结果和 MySQL 里的实时台账合并返回。
我建议不要直接改ruoyi-system这种核心模块,因为若依很多公共服务都挂在 system 模块上,改动风险大。新建一个业务模块专门承接达梦查询,逻辑更清晰,以后下线也方便。
4.2 完整配置清单
在 Nacos 中该服务对应的配置文件里,加入上一节那份 yml 配置。如果你本地还没有连达梦环境,建议先用一个测试达梦实例,确认连接正常后再上生产。达梦默认端口 5236,生产环境要确认防火墙规则,我吃过连接超时的亏,排查半天发现是安全组没放行 5236。
4.3 Mapper、Service、Controller 代码示例
建一个DmAccidentMapper,专门负责达梦库查询:
@DS("dm") @Mapper public interface DmAccidentMapper { List<DmAccident> selectAccidentList(@Param("beginTime") String beginTime, @Param("endTime") String endTime); }对应的 XML 写在DmAccidentMapper.xml中,表名、字段全部用达梦能识别的方式:
<select id="selectAccidentList" resultType="com.ruoyi.statistics.domain.DmAccident"> SELECT ID, ACCIDENT_NAME, ACCIDENT_TIME, ADDRESS FROM DM_HIS_ACCIDENT WHERE ACCIDENT_TIME >= TO_DATE(#{beginTime}, 'YYYY-MM-DD HH24:MI:SS') AND ACCIDENT_TIME <= TO_DATE(#{endTime}, 'YYYY-MM-DD HH24:MI:SS') </select>Service 层把两个数据源的结果合并:
@Service public class AccidentStatisticsService { @Autowired private RealTimeAccidentMapper realTimeAccidentMapper; @Autowired private DmAccidentMapper dmAccidentMapper; public List<AccidentVO> mergeAccidentData(String beginTime, String endTime) { List<AccidentVO> list = new ArrayList<>(); list.addAll(realTimeAccidentMapper.selectList(beginTime, endTime)); // 这里先调用 dm mapper,连接会从这里的数据源获取 List<DmAccident> dmList = dmAccidentMapper.selectAccidentList(beginTime, endTime); for (DmAccident dm : dmList) { AccidentVO vo = new AccidentVO(); // Bean 拷贝或手动转换 list.add(vo); } return list; } }Controller 就是普通接口,不在代码层面关心数据源:
@RestController @RequestMapping("/accident") public class AccidentController { @Autowired private AccidentStatisticsService accidentStatisticsService; @GetMapping("/merge") public TableDataInfo merge(@RequestParam String beginTime, @RequestParam String endTime) { return getDataTable(accidentStatisticsService.mergeAccidentData(beginTime, endTime)); } }这里有个细节要注意:在同一 Service 方法里访问两个库,物理上会有两次数据库连接的获取与释放。dmAccidentMapper因为有@DS,路由会切换到 dm;前面的 MySQL Mapper 没加@DS,默认走 master。两个操作不在同一个事务里,天然是两条独立连接,所以不会有事务锁连接的问题。但如果你在这个 Service 方法上加了@Transactional,那就要回到上一节说的事务边界问题。
4.4 验证流程和数据核对
启动服务之前,先确认 Nacos 配置已发布。启动时观察日志,dynamic-datasource 会打印加载到的数据源,类似:
dynamic-datasource initialized datasource: master dynamic-datasource initialized datasource: dm如果只看到 master,说明 dm 配置没有被读到,优先检查 Nacos 配置 key 是否正确、服务有没有正确拉取最新配置。启动成功后,调用/accident/merge接口,先确认两个库分别有数据,再看返回结果是否合并。为了调试路由是否生效,可以在 Mapper 接口加上一段简单的查询日志,或者临时在 Service 方法里打印当前数据源路由 key。我最常用的手段是写一个临时接口,返回DynamicDataSourceContextHolder.push后的当前 key,快速判断路由切换是否正常。
4.5 关于配置下沉到 Nacos 的提醒
很多人第一次在本地配置成功,部署到测试环境就发现连的还是本地 MySQL。原因是若依微服务的配置来源于 Nacos,本地启动类里配置的spring.cloud.nacos.config.name会决定它加载哪些配置。你改了本地 yml 不代表测试环境生效。上线时务必核实 Nacos 里对应环境、对应服务名下的配置已经同步修改,并且服务重启后生效。如果配置中心里有多个 dataId,注意让spring.datasource.dynamic这段配置放在该服务的主配置 dataId 中,避免被其他配置覆盖。
5. 踩坑记录与排查清单
5.1 达梦表名大小写导致“表或视图不存在”
这是接入达梦最常见的报错。MySQL 建小写表名,SQL 里写小写,连到达梦直接抛DMException说找不到表。解决办法不是我前面提到的“尽量建大写表”,而是从源头梳理表结构。如果项目里必须沿用现有小写表名,可以在创建达梦表时加双引号保留小写,查询 SQL 对应加双引号。但这样会让所有 SQL 都别扭,我最后还是把所有达梦表都改成了大写,Java 侧 SQL 也统一大写,一劳永逸。值得注意的是,达梦的字段名也一样,查询时最好明确列出字段,不要用SELECT *,否则字段映射很容易翻车。
5.2 分页 SQL 方言冲突
MySQL 的LIMIT在部分达梦兼容模式下能跑,但在非兼容模式下会报语法错误。用 PageHelper 的小伙伴尤其小心,因为 PageHelper 自动识别方言在多数据源场景下并不总是可靠。我在项目里就是把helper-dialect显式配成dm,并且让达梦相关查询单独走自己的一套 mapper,跟 MySQL 的 mapper 分开,彻底避免同一条 SQL 在两个方言之间摇摆。
5.3 @DS 不生效或 SQL 全走了默认库
如果发现加了@DS("dm")但查出来的还是 MySQL 数据,先自查三个点:
@DS注解是否被@MapperScan扫描到。@MapperScan只会扫描 Mapper 接口,不会处理 Service 上的注解,切换注解放在 Mapper 上时,确认 Mapper 接口被 Spring 代理而不是被原生 MyBatis 代理漏掉。- 同类内部调用。
@DS在事务和切面机制上与@DataSource类似,自调用会跳过切面。 - 事务绑定。方法上有
@Transactional时,连接已经绑定到事务,后续切换无效。
排查时可以打开 dynamic-datasource 的日志,或写一个临时接口打印路由 key,向前确认到底切没切。
5.4 多数据源下的事务失控
手动在多个数据源之间做写操作时,一定要想清楚事务边界。我一开始图方便,在合并写入接口上直接加了@Transactional,结果发现 MySQL 写成功了,达梦写失败了,MySQL 那边也已经提交,数据出现不一致。后来把方法拆开,MySQL 写操作独立事务,达梦写操作独立事务,中间失败用业务补偿处理,问题才解决。如果你的需求真是一个操作必须同时写两个库且不允许中间状态,那就老老实实引入 Seata 或者@DSTransactional,别指望 Spring 原生事务管两个数据源。
5.5 连接池参数对达梦的影响
达梦连接池的配置不能完全照抄 MySQL。达梦的连接建立比 MySQL 重一些,maxActive要根据并发量留余量,minIdle不要设太大,否则空闲连接占着达梦的 license 会话数。达梦对连接的空闲超时处理也和 MySQL 不太一样,如果出现“连接已被数据库实例关闭”的报错,通常要把连接池的validationQuery设置为SELECT 1 FROM DUAL,并开启测试连接。这里贴一段我在 Druid 下验证过的参数:
spring: datasource: dynamic: druid: initial-size: 5 min-idle: 5 max-active: 20 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: falseSELECT 1 FROM DUAL在两个库都能跑,这个验证 SQL 在多数据源环境里最通用。MySQL 里SELECT 1也行,但为了统一管理,所有数据源都用SELECT 1 FROM DUAL最省事。
最后再说一个实际操作中的心得:给达梦开的连接数一定要有单独的监控告警。达梦作为国产数据库,会话数限制比 MySQL 严格,连接池突然打满的时候,错误提示却往往是“无法创建连接”而不是“连接池满”,很容易误判成网络问题。我在这个项目里给 dm 数据源单独配了一个指标监控,看到连接数持续增长就知道要调整服务端会话数或连接池参数了。这个细节平时不会有人提醒你,但生产环境早晚会碰到。