首页 / 资讯中心 / 文章详情

若依微服务接入MySQL与达梦双数据源实战避坑指南

若依微服务接入MySQL与达梦双数据源实战避坑指南 ★ FEATURED ARTICLE
接手一个老项目改造任务主库是 MySQL业务方要求把一部分报表数据和历史归档数据放到国产数据库达梦DM上同时旧的业务接口还得继续读写 MySQL——说白了就是要给若依微服务接上 MySQL DM 多数据源。搜了一圈若依配 MySQL 双数据源的帖子一抓一大把但真正把达梦加进来的完整记录很少而且默认用的分页插件、代码生成器、事务写法到了达梦这儿全都会出幺蛾子。我前前后后折腾了三天整理了这篇配置实战记录把环境选型、Nacos 配置、代码接入、以及分页方言、事务失效、代码生成器这几个大坑尽量写清楚给正准备做类似改造的同学当个“避坑地图”。1. 先说清楚什么时候才真的需要“MySQL DM”双数据源动手之前先纠正一个观念双数据源不是炫技也不是因为某数据库“更好”它通常意味着系统正处于某种过渡状态或者天然就有多库分治的需求。想不清楚这一点后面的配置就是瞎忙。1.1 达梦不是“换皮的 MySQL”很多人第一次接触达梦会觉得它和 MySQL 差不多实际上达梦是一款独立研发的关系型数据库兼容多种主流数据库的语法模式。我这边测试库初始化的时候选了 MySQL 兼容模式所以很多 SQL 写起来确实是 MySQL 的写法。但这不代表你可以完全按 MySQL 的习惯在达梦上建表。最直观的几个差异达梦里的用户和模式schema是一一对应的登录用户本身就是默认模式。JDBC 连接里写的库名实际对应的是“某个用户下的模式”。默认标识符大小写敏感策略和 MySQL 不同表名、字段名建议统一大写下否则容易踩“表或视图不存在”的坑。自增主键支持 IDENTITY但如果建表用序列 触发器方案MyBatis 里主键生成的写法又会不一样。1.2 典型应用场景我这次遇到的情况是历史报表数据迁移到达梦常规业务数据留在 MySQL一个后台管理接口需要同时聚合两边的数据。类似场景还有几种过渡期双跑新库还没验收完旧库不能停两边同时写或同时读平滑切换。数据分级不同数据敏感度、时效性或归档策略不同放在不同数据库。异构查询汇聚报表查询要关联 MySQL 的业务表、达梦的历史表。这些场景的共同点是短期内没法把数据全量迁移成一个库但又必须在同一套微服务里把两边数据统一暴露给接口层。1.3 为什么不干脆只用一套库如果最终目标就是全量迁到达梦最省事的做法其实是“一刀切”而不是长期双库双写。但现实往往是这样迁移周期长、业务方需要并行验证、部分老接口依赖 MySQL 特有语法所以双数据源通常是过渡期最稳的选择。经验是要把双数据源当成一个“中间状态”能尽快收敛到单库就尽早收敛。长期维持双数据源后续的分布式事务、分页处理、监控成本会持续走高这个账一定要提前算。2. 动手前的四个关键确认版本、驱动、选型、依赖冲突配置双数据源前我习惯先把手边能确认的技术要素全部列出来。这一步看起来花时间实际能省下后面一大半的排查功夫。2.1 搞清楚你的若依微服务里谁在连库若依微服务RuoYi-Cloud按模块拆分和数据库直接相关的有这么几块ruoyi-system用户、角色、菜单这些系统业务数据一般也是我们改数据源的主战场。ruoyi-gen代码生成要用到的表结构信息它本身会连库查元数据。ruoyi-job定时任务如果任务里要跑业务 SQL也涉及数据源。所有服务的配置统一放 Nacos 配置中心数据源配置散落在各个服务自己的 yml 文件里。我这次要加双数据源的核心服务是 ruoyi-system所以改的是 Nacos 上 ruoyi-system-dev.yml。改之前先确定一件事你打算让哪个服务拥有达梦数据源是集中在 ruoyi-system 一个服务里还是多个服务各自配一套我建议过渡期集中在核心业务服务里减少配置面和排查面。2.2 达梦驱动版本必须和数据库匹配这个坑最不起眼但最容易出问题。达梦官方提供多个 JDBC 驱动 jar命名常见是 DmJdbcDriver16、DmJdbcDriver17、DmJdbcDriver18数字对应 JDK 版本。别只看名字随手引一个重点看它和达梦服务端版本的兼容性。驱动和服务端的版本跨度太大启动时可能出现协议不支持、无法登录一类的错误。我这边环境用的 JDK8 达梦 8引的是 com.dameng:DmJdbcDriver18:8.1.x.x。驱动类名是 dm.jdbc.driver.DmDriverURL 格式是 jdbc:dm://IP:端口/模式名默认端口 5236。驱动坐标我写的是当前验证过的形式实际引入时以 Maven 仓库里和你数据库版本对应的稳定版为准千万不要随意升级驱动版本。2.3 多数据源框架选型DS 还是 RoutingDataSource如果让你手写第一反应是用 Spring 的 AbstractRoutingDataSource 重写 determineCurrentLookupKey再配合 ThreadLocal 手动切换。这样不是不行但每一个数据源都要维护 mapping代码侵入大可读性差。我的选择是 dynamic-datasource-spring-boot-starter理由很简单DS 注解直接标在方法上切换逻辑和业务代码解耦。天然支持读写分离配置后面扩展从库不费劲。提供 strict 模式避免数据源 key 拼错时静默走默认库这个在开发阶段特别有用。这个框架核心思路是自定义 Spring 的 DataSource 路由逻辑在 AOP 层拦截带 DS 的调用把当前线程要用的数据源 key 塞进去。理解了这一层后面很多奇怪问题都好排查。2.4 依赖冲突druid 自动配置与 dynamic-datasource这里特别提醒若依微服务默认依赖 druid-spring-boot-starter而 dynamic-datasource 自己也管理连接池。两个东西同时存在很容易出现“明明改了 Nacos 配置但启动还是连原来的 MySQL”、或者 druid 监控页面读不到数据源的问题。我是直接把 druid 相关的自动配置排除掉让 dynamic-datasource 走默认连接池Hikari。多数据源场景少一个变量排查问题时更清爽。如果你确实要用 druid 做连接池也可以但要对 druid 的配置做适配复杂度会明显上升。我的判断是过渡期项目连接池稳定性比监控页面那点功能更重要先用默认池把链路跑通。3. 配置落地Nacos、DS、Mapper XML 一次讲清楚到这一步开始动真格的了。我按“配置中心 → 业务代码 → 映射文件 → 验证连通”的顺序带你过一遍。3.1 Nacos 里替换数据源配置下面是我在 ruoyi-system-dev.yml 里的实际配置结构注意我改了连接池管理方式新增了一个 dm 数据源。spring: datasource: dynamic: primary: master strict: false datasource: master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://172.16.10.20:3306/ry_cloud?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneGMT%2B8 username: ry_user password: ****** dm: driver-class-name: dm.jdbc.driver.DmDriver url: jdbc:dm://172.16.10.101:5236/DM_BUSINESS username: dm_app password: ******几个需要解释的点primary: master 表示不带 DS 的接口默认走 MySQL 数据源这个配置能让存量代码完全不动是非常关键的兼容性保障。strict: false 表示如果 DS 里写了不存在的 key比如手滑写成 dm1不会报错而是回退 primary。我建议开发环境开 strict: true让拼写错误直接暴露生产环境再根据情况关掉。达梦的 URL 里的 DM_BUSINESS 指的是达梦用户对应的模式名不是随便起的库名。这里如果写错启动不会报错但一执行查询就提示“模式不存在”。提示达梦里“用户”和“模式schema”是一一对应的。JDBC URL 里指定的这个字段实际要填达梦用户默认模式的名字而不是随意起的逻辑库名。3.2 Service 层用 DS 切换数据源写业务代码其实很简单核心就是在方法上打注解Service public class ReportServiceImpl implements ReportService { Autowired private ReportMapper dmReportMapper; Autowired private OrderMapper orderMapper; Override DS(master) public ListOrder listOrders() { return orderMapper.selectOrderList(); } Override DS(dm) public ListReport listReports() { return dmReportMapper.selectReportList(); } }几点注意事项DS 加在实现类的方法上比加在接口方法上更稳避免 CGLIB 代理时注解丢失。一个方法里如果要同时查两个库就先做成两个 service 方法分别打 DS再在调用方聚合不要在同一个方法里手动切数据源。如果用的是 MyBatis 的 Mapper 代理DS 也可以加到 Mapper 接口方法上但粒度太细维护成本高我一般只在 Service 层用方便统一管理。3.3 Mapper XML 和实体映射的细节达梦表映射到 MyBatis 时有几个注意点都是实测掉过坑的建表时建议字段名全部大写XML 里 resultMap 的 column 大小写尽量和实际表一致。不统一会出现明明查出了值但封装不到对象的诡异问题排查起来特别费劲。自增主键在 MySQL 用 useGeneratedKeystrue 加 keyPropertyid。达梦如果建表用的是 IDENTITY 也可以这么写如果用序列就要改成 selectKey。这个区别在代码生成和手写 XML 时都要留意。保留字要小心。比如达梦的 mode 关键字在你的表里可能就是普通字段但直接裸写会报语法错误。建议给字段加双引号或者干脆改字段名改造时我没少为这种问题返工。3.4 先验证连通再谈其他配置完先别急着跑全量接口。我建议做一个最小验证接口一个连 master 查用户表一个连 dm 查一张达梦里的表两个都能通再把业务接口放开。注意观察日志里打印的 SQL 语句。dynamic-datasource 在 debug 日志里会输出当前数据源 key从日志确认切换生效比看业务返回更直接。我见过有人配置完之后完全不看日志报表接口查出来的数据其实是 MySQL 的旧数据这种“假成功”比报错更危险。4. 分页、代码生成、事务三个最容易翻车的地方这次改造真正折磨人的不是配置本身而是周边生态的适配。若依这套框架里有些默认能力是“深度绑定 MySQL 的”到了达梦身上必须做调整。4.1 PageHelper 自动识别方言在达梦上不靠谱若依微服务全局用了 PageHelper 做分页startPage() 后面的第一条 SQL 会被自动拼接分页语句。默认情况下 PageHelper 会从连接元数据去猜数据库类型猜出来之后套用对应方言。问题就在这达梦的元数据它大概率识别不到准确类型或者识别成了一种和当前兼容模式不匹配的类型于是生成的 SQL 到了达梦那边就报语法错误。我这里有个很典型的报错现象列表接口一开分页就报“SQL 语法错误”去掉 startPage() 反而正常。解决办法不要依赖自动识别手工指定数据源的方言。如果你的达梦实例初始化成 MySQL 兼容模式就指定 mysql 方言如果初始化成 Oracle 兼容模式就指定 oracle 方言。比如在 Nacos 里给对应服务加pagehelper: helper-dialect: mysql reasonable: true实测下来把方言固定成和达梦实例一致的模式之后分页总数和列表数据都正常了。如果你对库的兼容模式不确定最简单的方式是拿一个 SQL 客户端手动跑一下 LIMIT 语法能用 LIMIT 就是 MySQL 兼容模式。表现可能原因处理方式分页 SQL 报语法错误方言识别错误生成语句与兼容模式不匹配手工指定 helper-dialect和库实例兼容模式保持一致分页总数不对达梦对 COUNT 语句的兼容处理差异检查总数 SQL 中是否有 MySQL 专属函数不带分页正常、带分页报错大概率是方言问题先在 SQL 客户端手动验证 LIMIT/ROWNUM 写法4.2 代码生成器连不上达梦若依的代码生成模块ruoyi-gen查询表结构默认走的是 MySQL 的 information_schema列、注释、主键全部从那里拿。到达梦这儿这个查询直接歇菜你会看到表信息为空、或者生成出来的代码目录是空的。我这边没有急着去改生成器因为过渡期用代码生成器的频率不高。如果你想完整支持思路是在 ruoyi-gen 里按现有代码的 dbType 扩展一套达梦的表结构查询 SQL参考 Oracle 分支的实现因为达梦的系统视图和 Oracle 的 ALL_TABLES、ALL_TAB_COLUMNS 更接近。这里还要配套改数据库类型到 Java 类型的映射规则工程量大一些。所以我的建议是过渡期先用人工建表先把业务接口跑通如果后续要频繁建表再专门排期补生成器的达梦支持。别让代码生成器成为双数据源落地的前置依赖。4.3 Transactional 和 DS 放在一起切换会失效这是这次排查耗时最长的一个问题。现象很诡异报表接口单独调用没问题但嵌套到某个带有 Transactional 的 service 流程里就报“表或视图不存在”而且明确报的是 MySQL 库里没有那张表。搞了半天才意识到Spring 的本地事务一旦开启Connection 就被绑定到当前线程动态数据源的路由在事务开始之后就没有意义了后续所有数据库操作都走的是事务一开始拿到的那个连接。也就是说方法体里先有 Transactional里面的 DS 切换并不会生效。对策很明确不带 DS 和带 DS 的逻辑不要放在同一个事务方法里拆成两个 service 方法分别调用。查询场景不要加传播级别默认的 Transactional就算改成 readOnly 也救不了这个问题本质是连接绑定不是读写策略。跨 MySQL 和达梦的多个写操作如果必须保证一致性别指望本地事务要上分布式事务。若依微服务本身带了 Seata 集成但达梦对 XA 事务的支持需要单独确认。我的建议是过渡期尽量避免跨库事务把双库写入的强一致场景尽量拆掉。注意本地事务开启后动态数据源切换会失效。这是开源版 dynamic-datasource 的既定行为不是 bug。设计接口时一定要把“跨库查询”和“事务操作”拆到不同的方法里。5. 连接池参数和安全习惯别等上线再补课业务代码跑通只是第一步上线前的参数适配和安全加固同样重要。这些问题如果拖到生产环境爆发排查成本会成倍上升。5.1 达梦的连接数不是越大越好很多同学给达梦数据源写的连接池参数直接复刻 MySQL结果达梦的连接数被打爆。我这边测试环境的达梦 license 有限制最大会话数就那么多。连接池 maxActive 一开始配成 100应用一启动占掉 20 个连接其他系统再连一下很快就提示超出最大连接数。我的建议是达梦连接池参数收敛着给spring: datasource: dynamic: datasource: dm: hikari: maximum-pool-size: 20 minimum-idle: 2 initialization-fail-timeout: 1 connection-timeout: 6000别小看 minimum-idle 和 initialization-fail-timeout 这两个参数。在达梦上min-idle 如果配大微服务每个实例启动都会占用固定数量连接多实例部署时这个数字会被放大。initialization-fail-timeout 配一个较短的值可以避免启动时因为连接池初始化慢而长时间卡住。5.2 账号密码别直接暴露在代码仓库里双数据源意味着新的数据库账号、密码、地址这些敏感信息尤其要管好。我这边做法是数据库配置全部走 Nacos且按环境分文件开发、测试、生产用不同命名空间互不干扰。达梦账号按最小权限创建只给业务需要的表和视图授予查询或 DML 权限不给 DBA 权限。达梦的权限体系接近 Oracle创建账号后按需授权不要图省事直接给全部权限。密码不提交到 Git不写在本地 yml 的明文里。Nacos 配置中心的访问权限也要控制避免任何开发人员都能看到生产库密码。5.3 排查时的日志和监控dynamic-datasource 在日志里会打印当前用的数据源 key排查“到底连了哪个库”时很有用。另外注意系统里所有直接用 MySQL 系统函数的地方比如 DATE_FORMAT、GROUP_CONCAT到了达梦上不一定兼容。我建议上线前做一次 SQL 兼容性自查把所有可能在 dm 数据源上执行的 SQL 都过一遍尤其是带 MySQL 专属函数的部分提前换成两边都能跑的写法。别等生产环境某个报表接口突然报错才回头查 SQL那时候你连它是哪条 SQL 触发的都要找半天。风险点建议MySQL 专属函数全局搜 DATE_FORMAT、GROUP_CONCAT 等替换为标准 SQL关键字冲突建表时避开达梦保留字或加双引号处理分页方言手工指定不依赖自动识别事务方法内切库拆方法本地事务内不做数据源切换连接池参数过大按达梦 license 会话数收敛配置6. 如果还要往前走读写分离和收敛路线双数据源跑通之后又会有新的衍生需求。我把自己尝试过和调研过的一些方向列在下面供你参考。6.1 顺带把读写分离做了dynamic-datasource 本身支持从库配置比如达梦可以配置一个只读从库专门承接报表查询。你把 dm 数据源扩展成 master/slave 结构查询接口用 DS(dm) 和 DS(dm_slave) 做切换即可。这个方案适合读多写少的报表场景能缓解主库压力配置上只是在动态数据源里多写一组 slave 节点成本很低。6.2 双库查询的另外几条路除了应用层聚合我还试过两条路线达梦侧的视图访问 MySQL不太现实达梦和 MySQL 之间做跨库链路成本高也不稳定。用数据同步工具把 MySQL 的热点数据定期同步到达梦这样报表查询只打达梦一个库应用层就回到了单数据源事务问题也没了。如果过渡期拉长我更倾向第二种方案与其让应用长期维护双数据源不如把数据同步做好让查询面收敛到一个库。毕竟代码里的 DS 越多未来的维护成本越高。6.3 几点体会这套 MySQL DM 多数据源配置本身不难难的是周边生态的适配。分页要指定方言、代码生成器要有新类型、事务要避开本地事务的绑定逻辑、连接池要考虑 license 限制。我的做法是先小范围验证一个模块跑通分页和查询再逐步扩大范围而不是一次把全部服务都切过去。如果时间往回倒我甚至会把代码生成器的适配也放到第一波来做因为后面频繁建表的时候手工建模真不轻松。最后分享一个小习惯每次在 Nacos 改完数据源配置我会先重启一个服务实例抓启动日志确认数据源初始化成功再滚到其他实例。双数据源的环境里“配置已保存”和“配置已生效”之间经常会隔着一个缓存或者一个需要排查很久的问题。稳一点不丢人。
阅读完成 · 觉得有帮助?
咨询建站