最近接手了一个信创改造项目分布式任务调度这块用的 XXL-JOB管理端的数据要落到达梦数据库。XXL-JOB 默认只支持 MySQL官方没有提供达梦适配只能自己动手。折腾了几天我踩过的坑、改过的地方、验证过的流程都整理在下面。如果你正准备把 XXL-JOB 的调度中心从 MySQL 迁到达梦或者只是想知道国产数据库迁移会碰到哪些坑这篇记录可以直接参考。先说结论XXL-JOB 接达梦不是简单地换一个 JDBC 驱动就能跑起来涉及建表脚本、MyBatis 映射、连接池配置、打包方式等多个层面。但整个改造量不算大核心改动就集中在几个文件里。下面从动机、环境、源码、联调、验证几个维度完整讲一遍。1. 为什么要把 XXL-JOB 拉到达梦这条船上1.1 XXL-JOB 的存储设计把 MySQL 焊死了XXL-JOB 分调度中心admin和执行器executor两大部分。调度中心本身就一个 Spring Boot 应用任务信息、调度日志、执行器注册信息全部存在关系型数据库里。官方源码里只提供了 MySQL 版本的建表脚本和管理端的数据访问实现SQL 方言大量依赖 MySQL 特性表结构主键用的是AUTO_INCREMENT数据更新喜欢用REPLACE或者ON DUPLICATE KEY UPDATE字段类型也默认 MySQL 的TEXT/LONGTEXT/DATETIME。所以一旦把存储换成达梦表面上只是换个数据库实际上要处理的是方言差异、MyBatis SQL 兼容性、JDBC 驱动行为差异这几个问题。1.2 达梦数据库到底兼容到什么程度达梦数据库DM8整体语法偏向 Oracle但也提供了 MySQL 兼容模式。开了兼容模式之后很多东西能直接用比如AUTO_INCREMENT、LIMIT分页、NOW()、IFNULL()。但兼容不代表一模一样有几个点要特别留意自增列达梦在兼容模式下能识别AUTO_INCREMENT但某些场景下我更推荐用官方推荐写法IDENTITY(1,1)尤其在老版本驱动里后者的主键回填更稳定。注释语法MySQL 建表时字段名 类型 COMMENT 说明达梦要求写成COMMENT ON COLUMN 表.字段 IS 说明这个是导入脚本时最容易报错的地方。大小写敏感达梦默认对未加引号的标识符处理成大写而 XXL-JOB 的表名、字段名都是小写一旦建表脚本把表名建成了大写MyBatis 里去查小写表名就会报“表或视图不存在”。锁机制达梦在并发更新同一行时锁等待的直观表现和 MySQL 不太一样高并发调度日志写入时更容易看到锁等待。理解这些差异比背一堆配置词有用得多。1.3 不换框架的前提三个最低要求市面上分布式任务调度框架不少有的原生支持国产数据库但团队已经深度使用 XXL-JOB任务脚本、路由策略、失败重试等逻辑都跑得很稳冒然换框架风险更大。所以我给自己定了三个前提调度中心的管理端继续用 XXL-JOB不改业务交互方式执行器侧的接入方式保持不变现有任务不用重写改造后的部署包能独立分发不依赖本地 IDE、不依赖临时中间件。基于这三点唯一合理路线就是直接改 XXL-JOB 的 admin 端代码让它适配达梦。虽然要动源码但改动面可控后续也能够把修改记录沉淀下来。2. 先把库准备好再谈改代码2.1 达梦实例初始化时的兼容模式与参数选择建库之前先检查达梦实例的兼容模式。我这边装的是 DM8在初始化实例时如果选了“兼容 MySQL”很多 SQL 解析就按 MySQL 的规则走后面可以减少大量改造。如果实例已经建好也可以在dm.ini里修改COMPATIBLE_MODE参数改成 MySQL 兼容对应的值不同小版本具体数字不完全一样安装文档里会标常见的是 3改完重启实例生效。其他几个参数我在初始化时也专门调过字符集选 UTF-8不然中文任务名、执行器名称存进去会乱码页大小建议 16K 或 32K。调度日志表xxl_job_log数据量增长非常快页太小会导致单页能存的行数变少索引扫描和日志写入性能都会有影响归档模式要结合业务需求设置。如果任务调度频率高数据库会产生大量归档日志磁盘不够的话调度中心运行一段时间后会有各种连接问题。2.2 建用户、建 schema权限一次到位达梦的逻辑结构和 MySQL 不太一样用户和 schema 是一对一关系创建用户时默认会带一个同名 schema。我建议单独建一个XXL_JOB用户所有 XXL-JOB 表都放在这个用户对应的 schema 下避免和业务表混在一起。CREATE USER XXL_JOB IDENTIFIED BY your_password; GRANT DBA TO XXL_JOB;我这里直接给了 DBA 权限图的是省事。生产环境建议最小权限至少要有CREATE TABLE、SELECT、INSERT、UPDATE、DELETE因为调度中心运行过程要写任务日志、更新执行器注册状态这几个权限缺一不可。后续连接串里要显式指定 schema。我见过不少人卡在这一步admin 能启动但一操作就提示找不到表就是因为没有指定 schema。jdbc:dm://127.0.0.1:5236?schemaXXL_JOB2.3 建表脚本迁移哪些 SQL 要手工改XXL-JOB 官方提供的tables_xxl_job.sql是 MySQL 方言直接把整个文件丢给达梦执行大概率会在 COMMENT 语法、存储引擎声明、自增列这几个位置报错。我改造建表脚本时主要做了这几件事去掉ENGINEInnoDB DEFAULT CHARSETutf8mb4这类 MySQL 专属配置把AUTO_INCREMENT改写成IDENTITY(1,1)把字段后的COMMENT xxx移除统一改成达梦的COMMENT ON COLUMN语句检查表名和字段名大小写确保全部小写。不要小看这些细节我第一版脚本在 DM 管理工具里执行后表面上看 12 张表都建成功了但注释全部丢失后面排查问题时特别费劲。2.4 从 MySQL DDL 到达梦 DDL 的实测转换样例以xxl_job_info为例官方 MySQL 建表语句里有一段是这样的CREATE TABLE xxl_job_info ( id int(11) NOT NULL AUTO_INCREMENT, job_group int(11) NOT NULL COMMENT 执行器主键ID, job_desc varchar(255) NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;改造后到达梦里是这样的CREATE TABLE XXL_JOB.XXL_JOB_INFO ( ID INT IDENTITY(1,1) NOT NULL, JOB_GROUP INT NOT NULL, JOB_DESC VARCHAR(255) NOT NULL, PRIMARY KEY (ID) ); COMMENT ON COLUMN XXL_JOB.XXL_JOB_INFO.JOB_GROUP IS 执行器主键ID;注意表名和字段名我都统一用了大写。这里有个关键点MyBatis 里查询语句写的是小写列名达梦默认不区分大小写但前提是查询语句里的标识符没有加引号。如果 XML 里写了带双引号的小写列名达梦会严格区分大小写导致“无效的列名”后面源码改造部分会专门讲。3. 源码改造的每一步驱动、配置、Mapper3.1 达梦 JDBC 驱动的正确引入方式达梦官方 JDBC 驱动不在 Maven 中央仓库除非公司私服有否则没法直接通过groupId:artifactId:version拉取。我直接把驱动 jar 放到了 admin 模块的lib目录下用systemscope 引入dependency groupIdcom.dameng/groupId artifactIdDmJdbcDriver18/artifactId version8.1.2/version scopesystem/scope systemPath${project.basedir}/lib/DmJdbcDriver18.jar/systemPath /dependency这里有个打包陷阱。Spring Boot 默认不会把systemscope 的 jar 打进可执行 fat jar如果不额外配置本地 IDE 跑着正常一部署到服务器就报ClassNotFoundException: dm.jdbc.driver.DmDriver。解决方案是在spring-boot-maven-plugin里打开includeSystemScope后面打包部分我再细说。3.2 application.properties 中的数据源参数XXL-JOB admin 的数据源配置在admin/src/main/resources/application.properties把原来的 MySQL 参数改成达梦spring.datasource.urljdbc:dm://127.0.0.1:5236?schemaXXL_JOB spring.datasource.usernamexxl_job spring.datasource.passwordyour_password spring.datasource.driver-class-namedm.jdbc.driver.DmDriver spring.datasource.typecom.zaxxer.hikari.HikariDataSource spring.datasource.hikari.connection-timeout30000 spring.datasource.hikari.connection-test-querySELECT 1 spring.datasource.hikari.maximum-pool-size20 spring.datasource.hikari.minimum-idle5 spring.datasource.hikari.max-lifetime1500000connection-test-querySELECT 1这一行建议必加。不同版本的达梦驱动对 JDBC4 的isValid()支持程度不一样HikariCP 在拿连接、归还连接时如果发现连接已经失效会触发回收逻辑但探测 SQL 更稳妥。max-lifetime设成 25 分钟是为了避免达梦服务端主动断开空闲会话后连接池还拿着旧连接不放这个坑我后面还专门遇到过。3.3 三个必须动手的 MyBatis 映射点XXL-JOB admin 的 SQL 都在admin/src/main/resources/mybatis-mapper/目录下XML 文件名和 Mapper 接口一一对应。我改造时不是一上来就大改特改而是边跑边看日志最后真正需要改的核心点有三个。第一分页写法。调度日志查询列表用到LIMIT #{offset}, #{pagesize}这个语法在达梦 MySQL 兼容模式下可以正常用但如果你在非兼容模式下跑会因为达梦不识别这个格式而报错。我的建议是保留LIMIT写法因为代码可读性最好但前提是实例开启了 MySQL 兼容模式。如果实例没法改那就要把分页 SQL 改成达梦支持的写法或者用 MyBatis 分页插件统一处理。第二注册信息更新。xxl_job_registry表保存执行器在线状态原来的 MySQL 写法可能用了REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE达梦不认这些需要改成MERGE INTO。我一开始只是把REPLACE INTO改成DELETE INSERT结果在多个调度中心并发注册时出现数据被清掉的问题后来改成MERGE INTO才稳定。MERGE INTO xxl_job_registry t USING ( SELECT #{registryGroup} AS registry_group, #{registryKey} AS registry_key, #{registryValue} AS registry_value ) s ON (t.registry_group s.registry_group AND t.registry_key s.registry_key) WHEN MATCHED THEN UPDATE SET t.registry_value s.registry_value, t.update_time NOW() WHEN NOT MATCHED THEN INSERT ( registry_group, registry_key, registry_value, update_time ) VALUES ( s.registry_group, s.registry_key, s.registry_value, NOW() )第三主键回填。MyBatis 中useGeneratedKeystrue keyPropertyid依赖 JDBC 驱动返回自增主键。达梦开发版驱动某些版本对IDENTITY列的主键回填支持不够好导致插入后拿不到主键任务创建后刷新列表就报空指针。解决办法是升级驱动版本或者把建表方式从IDENTITY改成序列加触发器但那是万不得已的方案不建议一开始就搞。3.4 容易被忽略的 schema 和大小写问题这是整个改造中让我耗时最久的一个问题。现象是 admin 能启动登录页能打开但任务列表、执行器列表全是空的后台日志偶尔报“无效的表名”。查到最后发现两个原因叠加第一连接串里没带schema达梦默认去当前用户同名 schema 找表。如果建表脚本执行时用的是SYSDBA那表建到了SYSDBA的 schema 下而应用用XXL_JOB用户连接自然查不到。第二MyBatis XML 里如果写了带双引号的列名比如id达梦会认为这是大小写敏感的对象名。而建表时如果表结构里列名是大写ID查询写id就报列不存在。改造时我统一规范XML 里不写引号让达梦走默认大小写转换建表脚本也保持一致问题才彻底解决。4. 联调踩坑实录注册不上、连接失败、日志空白4.1 执行器注册列表为空的定位链路迁移后的第一个大坑是调度中心管理界面里“执行器管理”能看到手动添加的分组但“在线机器”列表始终为空。第一反应是数据库没写好去查xxl_job_registry表结果里面确实没有数据。后来按这条链路排查才发现不是数据库兼容问题确认执行器进程有没有起来。直接看执行器控制台日志有没有打印注册成功的字样确认执行器配置的xxl.job.admin.addresses地址能不能被执行器服务器访问。如果 admin 部署在内网执行器在另一网段端口不通就会注册失败确认执行器有没有配置固定ip。如果机器多网卡自动探测 IP 经常探测到 docker 网卡或虚拟网卡注册过去也是无效地址最后回到达梦表里看xxl_job_registry我这次排查时发现表里确实没有数据但真相不是表建错了而是执行器的地址配置里用了 admin 的机器名执行器解析不了。这个问题和达梦没有直接关系但迁移后很容易让人误判为数据库兼容问题白白浪费几个小时。排查顺序应该是网络 - 配置 - 数据库不要反过来。4.2 连接池超时和达梦服务端会话配置admin 跑了一天后控制台开始出现大量这样的报错Connection is not available, request timed out after 30000ms当时第一直觉是连接池太小把maximum-pool-size调大结果没用。后来去达梦服务端看会话数发现大量空闲会话被数据库主动断掉而 HikariCP 里还残留着这些连接再拿连接时才发现已经失效。解决办法是双向配置达梦服务端dm.ini里检查会话超时参数适当调大HikariCP 里把max-lifetime设置为小于数据库会话超时时间例如 25 分钟显式配置connection-test-querySELECT 1让连接池每次取连接时做一次有效性探测。这个组合拳打完连接池问题再没出现。如果是长时间挂机跑批的场景这个问题一定会遇到建议提前配好。4.3 调度日志查不出数据时的“无效列名”处理另一个高频问题是在调度中心点击“调度日志”时列表接口报 500日志里写着无效的列名。我当时的第一反应是某个字段在达梦里不存在结果翻遍了表结构也没发现缺列。最后逐条 SQL 排查才找到原因MyBatis XML 里某个if条件拼接出的 SQL用了反引号包裹字段名。MySQL 允许反引号达梦不认识反引号直接当成字符串或者语法错误。处理方式是把 XML 里所有反引号去掉。这里还要提一句不要用全局搜索替换把注释里的反引号也删掉容易误伤逐文件确认更稳。5. 一份可以直接参考的源码改造清单5.1 文件级改动对照表整个改造不像很多人想的那样要换掉底层框架反而改动面很小。我最后整理出来的对照关系如下文件改动内容admin/pom.xml引入达梦 JDBC 驱动 jar配置includeSystemScopeadmin/src/main/resources/application.properties数据源 URL、驱动类、连接池参数doc/db/tables_xxl_job_dm.sql达梦版建表脚本新建文件保留官方原版便于对照admin/src/main/resources/mybatis-mapper/XxlJobRegistryMapper.xml注册信息写改用MERGE INTOadmin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml分页写法确认、反引号清理、大字段处理admin/src/main/resources/mybatis-mapper/XxlJobInfoMapper.xml主键回填兼容性调整我没有改动 XXL-JOB 的版本号也没有重构核心调度逻辑只做必要的数据库方言适配。这样做的最大好处是达梦适配的 diff 非常清晰将来 XXL-JOB 官方出新版本可以直接把补丁重新打上去不用从头挖代码。5.2 打包时把达梦驱动带进可执行 jar前面提过systemscope 的 jar 不会默认打进 Spring Boot 可执行 jar这里给完整配置plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration includeSystemScopetrue/includeSystemScope /configuration /plugin打包后用这条命令检查jar tf xxl-job-admin-*.jar | grep DmJdbcDriver如果能看到BOOT-INF/lib/DmJdbcDriver18.jar说明驱动进去了。这一步我建议在改造完成后立刻验证别等到上服务器才发现包有问题。5.3 建表脚本和数据脚本分开管理最初我把基础数据比如默认执行器分组、默认用户也写进了建表脚本结果测试环境重建时误插了重复数据导致页面上出现两个“默认执行器”。后来我把tables_xxl_job_dm.sql和data_xxl_job_dm.sql分开建表和初始化数据分两步执行。团队合作时我会要求每个环境执行顺序固定先建用户再跑表结构再灌基础数据最后启动 admin。这样出问题能很快定位是哪个环节。6. 应用跑起来之后的性能与稳定性优化6.1 调度日志增长太快如何按达梦特性清理调度中心的xxl_job_log是增长最快的表。任务每次触发都会写一条触发日志执行完成后还要更新处理结果日志量上去了查询调度日志页面就会变慢。XXL-JOB 本身有日志自动清理但它是基于日志保存天数底层执行的是DELETE。达梦里对大表做 DELETE 会产生大量归档日志如果在业务高峰期触发清理可能把数据库 IO 打满。我的做法是把自动清理调到业务低峰期历史日志归档到独立表或者干脆定期导出后删除如果确认日志不再需要可以配合运维在维护窗口做TRUNCATE效果比 DELETE 好很多。6.2 多调度中心并发下对达梦数据库的压力有些场景会部署多个 admin 实例做负载均衡所有实例共用一张xxl_job_registry和xxl_job_log这时达梦的锁机制成了瓶颈。调度日志的插入和xxl_job_registry的更新会并发操作同一组表我在实测中发现任务量到了每秒几十次时数据库偶尔会出现行锁等待。优化方向有这几个不要让多个 admin 都启用同一批执行器按业务线拆分到不同 admin 组日志表定期归档把数据量控制在百万行以内达梦的索引设计要针对查询条件单独做不要照着 MySQL 原样抄。6.3 顺带能复制到其他国产库的适配套路这次改造跑通之后我发现这套方法可以平移到其他国产数据库比如人大金仓、openGauss 之类套路基本一致先确认数据库兼容模式能开 MySQL 兼容就开能少改很多 SQL建表脚本做一遍方言转换重点处理自增列、注释、反引号、存储引擎声明换 JDBC 驱动把数据源参数调整到让连接池稳定工作逐个跑任务、执行器注册、日志查询三个核心功能有问题就单独改对应 Mapper 文件。这套流程跑下来优势在于不用理解 XXL-JOB 全部源码只需要关注存储相关代码对新上手的人非常友好。7. 功能验收流程与实测结果7.1 六步冒烟测试改造完成后我按下面的顺序做了验收建议你也照着走一遍。任何一步出问题都能很快定位到是数据库兼容问题还是配置问题。启动 admin登录并打开任务列表、执行器列表页面确认接口不报错新建一个执行器分组保存后在执行器列表能看到记录刷新后记录不丢新建一个测试任务Cron 表达式设为每分钟一次处理逻辑随便写一个打印日志等待任务触发打开调度日志确认有成功记录且执行器返回的结果能回写到达梦重启 admin确认任务信息还在执行器自动注册并重新出现在在线列表里手动停止执行器等下一个调度周期确认调度日志里能记录执行失败而不是接口报错。我验收时跑了 3 个执行器节点、100 个任务一个晚上产生 8 万多条调度日志达梦读写正常没有出现连接池泄漏和锁死。7.2 达梦库上的索引与执行计划调整跑了一段时间后我发现“调度日志”页面按时间倒序翻页有点慢。去达梦管理工具看执行计划发现日志查询走了全表扫描因为xxl_job_log的索引还保留着 MySQL 那套。后来我根据实际查询条件单独加了一个组合索引CREATE INDEX IDX_XXL_JOB_LOG_TRIGGER_TIME ON XXL_JOB.XXL_JOB_LOG(TRIGGER_TIME);加完索引后按时间范围查询秒回。这个案例提醒我跨数据库迁移时索引不是复制过去就完事一定要结合达梦优化器行为重新验证。7.3 Windows/Linux 环境验证差异说明有人问手边没有 Linux 服务器能不能在 Windows 上先做适配验证完全可以。达梦官方提供 Windows 安装包安装后创建实例、改兼容模式、执行脚本流程和 Linux 几乎一致。差异主要是dm.ini路径不同Windows 在安装目录的data下修改前记得停掉达梦服务。我在 Windows 上跑通整个流程后再把 jar 和 SQL 脚本部署到 Linux 生产环境应用代码和数据库脚本没有做任何额外修改。这也说明这套改造方案的环境依赖非常少真正做到了“一次适配多端复用”。最后分享一个排查技巧。改造过程中如果看到 SQL 语法错误先不要急着怀疑达梦不兼容优先检查三件小事表名和列名大小写、有没有反引号、有没有REPLACE INTO或ON DUPLICATE KEY UPDATE。这三类问题排掉之后九成的 SQL 都能在达梦 MySQL 兼容模式下原样运行。动手前先确认数据库版本和兼容模式部署后验证连接池和驱动打包情况做到这三件事XXL-JOB 接达梦基本不会再有意外。