资讯动态

XXL-Job从MySQL迁移到Oracle的完整改造实践

发布时间:2026/10/19 11:33:05 来源:尧图企业网站定制
做 Java 后端的应该都清楚XXL-Job 是开源任务调度框架里使用率很高的一个部署轻、界面全、和 Spring Boot 集成也方便。但我们最近碰到一个硬性要求客户的生产环境不允许使用 MySQL所有基座必须落在 Oracle 上。于是我对 XXL-Job 做了一次完整的数据源改造从建表脚本、Mapper SQL、主键生成到事务边界全部过了一遍。整个过程比想象中琐碎很多坑并不是查文档能立刻解决的所以整理出这份改造纪实给后面要接同样需求的朋友参考。1. 为什么需要改造XXL-Job 与 Oracle 的底层差异1.1 XXL-Job 默认存储选型与实际改造场景XXL-Job 调度中心本身自带一套管理页面负责维护任务信息、执行器注册、调度日志、触发锁等等。这些数据默认落在 MySQL 里官方文档中给的初始化脚本也全是 MySQL 的 DDL。实际上手之前我以为只要改一下 JDBC 连接串、换一下驱动就能跑但很快发现完全不是这么回事。XXL-Job 的 Mapper XML 里大量使用了 MySQL 的LIMIT分页、IFNULL函数、GROUP_CONCAT聚合以及依赖自增主键的useGeneratedKeys。这些在 Oracle 里要么语法不兼容要么数据类型差异很大所以“只改连接串”这个想法基本可以放弃。真实改造场景往往是两种第一种是新建项目但企业要求统一使用 Oracle 作为核心库第二种是存量系统需要从 MySQL 迁移到 Oracle比如金融、能源类客户对数据库品牌有硬性规定。我们遇到的是第二种任务数据本身不算特别多但调度日志增长很快所以还需要额外考虑 Oracle 下大批量数据删除和归档的问题。1.2 MySQL 与 Oracle 方言差异全景如果用一句话概括这次改造的工作量那就是“把所有 MySQL 独有的特征从持久层代码里剥离掉”。为了让你心里有数我把影响最大的几类差异先列出来差异点MySQL 写法Oracle 写法分页LIMIT #{offset}, #{size}或LIMIT #{size}Oracle 12cOFFSET #{offset} ROWS FETCH NEXT #{size} ROWS ONLY11gROWNUM包一层子查询主键AUTO_INCREMENTuseGeneratedKeys序列SEQ.NEXTVALselectKey或触发器空值函数IFNULL(expr, 0)NVL(expr, 0)日期函数NOW()、CURDATE()、DATE_FORMAT()SYSDATE、TRUNC(SYSDATE)、TO_CHAR()字符串聚合GROUP_CONCAT(name SEPARATOR ,)LISTAGG(name, ,) WITHIN GROUP (ORDER BY id)条件判断IF(cond, a, b)CASE WHEN cond THEN a ELSE b END字符串拼接CONCAT(a, b)a || b或两参数CONCAT布尔类型TINYINT(1)NUMBER(1)表注释COMMENT说明COMMENT ON TABLE/COMMENT ON COLUMN更新锁SELECT ... FOR UPDATE同样支持但要注意事务隔离级别和索引命中这些差异不是零散的小问题而是会贯穿建表、查询、写入、清理全链路。所以改造前最好花半天时间把所有 Mapper XML 里的 SQL 先扫一遍凡是出现上述 MySQL 特性的地方都标记出来后面逐个替换。1.3 改造范围的正确评估很多第一次做数据库迁移的同学容易漏掉改造点。我建议按“DDL 脚本、Mapper XML、数据源配置、部署运维”四个维度拆解范围。DDL 脚本层面需要把官方提供的tables_mysql.sql转成 Oracle 版包括所有建表语句、索引、约束和注释。Mapper XML 层面需要检查XxlJobInfoDao、XxlJobLogDao、XxlJobRegistryDao等所有 DAO 对应的 SQL重点关注 insert、分页查询、统计报表和清理语句。数据源配置层面除了驱动依赖还要注意连接池参数、Oracle 的 Schema 与权限。部署运维层面要提前想清楚日志表的归档策略、索引设计以及慢查询监控。我们团队一开始只安排了两个人改代码后来发现 DDL 转换和 SQL 改写同步推进还要兼顾老数据迁移明显人手不足。如果你们的项目周期紧建议把改造、数据迁移、回归验证分成三条线并行推进否则很容易在 Oracle 的方言细节上反复返工。2. 数据库初始化脚本转换从 MySQL DDL 到 Oracle DDL2.1 建表语句转换细节Oracle 和 MySQL 的建表语法差异在 XXL-Job 这种多表结构上体现得非常充分。先看一个典型的任务表转换。MySQL 原始脚本大概是CREATE TABLE xxl_job_info ( id int(11) NOT NULL AUTO_INCREMENT, job_group int(11) NOT NULL, job_desc varchar(255) NOT NULL, add_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;转成 Oracle 后CREATE TABLE xxl_job_info ( id NUMBER(20) NOT NULL, job_group NUMBER(10) NOT NULL, job_desc VARCHAR2(255) NOT NULL, add_time TIMESTAMP DEFAULT SYSTIMESTAMP, update_time TIMESTAMP DEFAULT SYSTIMESTAMP, PRIMARY KEY (id) );这里有几个容易踩的细节。第一Oracle 里建议用NUMBER而不是INT长度可以根据 Java 侧的 Long 和 Integer 来选择id用NUMBER(20)常规业务字段用NUMBER(10)。第二字符串类型首选VARCHAR2不要图省事全部用VARCHAR虽然 Oracle 目前能兼容但内部语义和长度计算还是有一些差别。第三日期时间类型建议用TIMESTAMP因为 XXL-Job 的任务调度会精确到毫秒如果用了DATE虽然也包含时间但在 JDBC 映射到java.util.Date时容易出现精度截断。另外MySQL 里TINYINT(1)表达布尔值Oracle 里通常用NUMBER(1)。XXL-Job 的trigger_status、glue_type这类字段如果原来是TINYINT需要统一确认 Java 侧的类型映射。如果原工程里已经用了Integer类型接收那么NUMBER(10)会更稳妥避免 MyBatis 的TypeHandler在取值时出现转换异常。2.2 索引、约束与注释处理Oracle 里的索引名、约束名在同一个 Schema 下必须唯一这个和 MySQL 的表内索引名唯一不是一回事。XXL-Job 默认脚本里的索引名都比较短比如idx_xxl_job_log_trigger_time直接搬过来通常没冲突。但如果你在改造过程中为了多个表增加了相同前缀的索引就很容易撞名。我们遇到过一次idx_trigger_time在两个表上重复导致建第二个索引时报 ORA-01408。注释这块也很容易被忽略。MySQL 可以在建表语句里直接写列注释job_desc varchar(255) NOT NULL COMMENT 任务描述,Oracle 没有这种内联写法需要单独执行COMMENT ON TABLE xxl_job_info IS 调度任务信息表; COMMENT ON COLUMN xxl_job_info.job_desc IS 任务描述; COMMENT ON COLUMN xxl_job_info.job_group IS 执行器主键ID;如果表很多建议写一个独立的注释脚本保持可追溯。注释脚本在开发库里跑一遍之后还要检查一下USER_TAB_COMMENTS和USER_COL_COMMENTS确认是否真的写进去了。这个步骤看起来简单但很多团队迁移后管理页面字段显示不友好十有八九就是漏了注释。2.3 主键序列与触发器设计Oracle 的经典主键方案是序列加触发器。如果使用 Oracle 12c 及以上版本也可以考虑 Identity 列但考虑兼容性和与 MyBatis 的配合我推荐用序列。先创建序列CREATE SEQUENCE SEQ_XXL_JOB_INFO START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;注意NOCACHE的选择取决于插入并发量。如果调度中心在一个实例内运行NOCACHE没什么问题如果未来要做多实例CACHE 20能减少序列争用但数据库异常重启时会有少量跳号。调度任务本身允许 ID 不连续所以用CACHE完全可行。如果有历史数据需要先把序列当前值设置到已有数据的最大 ID 之后。可以通过存储过程判断也可以手工查一下SELECT MAX(id) FROM xxl_job_info;再DROP SEQUENCE重建更简单的做法是生成序列后执行一段匿名的 PL/SQL 调整序列值。序列只是第一步真正要让 MyBatis 拿到生成的 ID还要改造 Mapper 的 insert 语句这个放到下一节细说。3. Mapper 层 SQL 改造核心代码逐个替换3.1 分页查询改写与 PageHelper 陷阱XXL-Job 管理后台里任务列表、调度日志列表都有分页查询。MySQL 下最常见的写法是SELECT * FROM xxl_job_log WHERE job_id #{jobId} ORDER BY trigger_time DESC LIMIT #{offset}, #{pagesize};Oracle 12c 及以上可以直接用SELECT * FROM xxl_job_log WHERE job_id #{jobId} ORDER BY trigger_time DESC OFFSET #{offset} ROWS FETCH NEXT #{pagesize} ROWS ONLY;如果你还在维护 Oracle 11g那么就只能用 ROWNUM 方案。这里有一个非常经典的坑先排序再取前 N 条和多层 ROWNUM 的写法容易写错。正确思路是先按时间排序得到有序结果集再在外面包一层做 ROWNUM 过滤SELECT * FROM ( SELECT inner_log.*, ROWNUM AS rn FROM ( SELECT * FROM xxl_job_log WHERE job_id #{jobId} ORDER BY trigger_time DESC ) inner_log WHERE ROWNUM #{offsetEnd} ) WHERE rn #{offset};如果 XXL-Job 的 Mapper 里用了 PageHelper 的PageHelper.startPage()那么在 Oracle 下要特别注意方言配置。PageHelper 会自动拼接分页语句但它的自动方言识别有时会被驱动版本干扰。我们这次没有改动 PageHelper 的相关代码而是选择把所有手写 SQL 里的LIMIT全部替换成 Oracle 兼容写法理由很简单改造期间改动越少回归风险越低。这里要特别提一下Oracle 的OFFSET FETCH在 12.1 之后的版本才可用。如果你的客户用的还是 11g那不管代码怎么写都别指望FETCH FIRST老老实实走 ROWNUM 方案。一个项目里的所有分页 SQL 尽量统一成同一种方案不要有的用 ROWNUM有的用 OFFSET FETCH后续维护会非常痛苦。3.2 插入语句主键方案改造这是整个改造过程中最容易被忽视的一环。MySQL 下 insert 可以直接依赖自增主键MyBatis 里靠useGeneratedKeystrue把自动生成的 ID 回填到 Java 对象里。Oracle 没有这个机制必须先把序列值查出来再插入。我的做法是在 insert 语句前面增加selectKey。以任务信息表为例insert idsave parameterTypecom.xxl.job.admin.core.model.XxlJobInfo selectKey keyPropertyid resultTypejava.lang.Long orderBEFORE SELECT SEQ_XXL_JOB_INFO.NEXTVAL FROM DUAL /selectKey INSERT INTO xxl_job_info ( id, job_group, job_desc, add_time, update_time ) VALUES ( #{id}, #{jobGroup}, #{jobDesc}, #{addTime}, #{updateTime} ) /insert注意selectKey的order必须设置成BEFORE并且keyProperty要和实体类的id属性一致。如果原来 insert 里useGeneratedKeystrue keyPropertyid在切换后要果断删掉否则 MyBatis 会尝试从Statement里拿自动生成的键Oracle 驱动下很可能报错或者拿到空值。序列名称也要注意大小写。Oracle 默认把未加引号的标识符转换成大写所以如果你创建的是SEQ_XXL_JOB_INFO那么 SQL 里写SEQ_XXL_JOB_INFO.NEXTVAL没问题。如果某个人工建库的序列用了小写加引号那 SQL 里的引用也必须带引号且大小写一致否则就会报ORA-02289: sequence does not exist。3.3 常用函数替换对照与通用 SQL 规范XXL-Job 的日志统计、执行器注册列表、调度报表这几个模块里函数依赖非常多。比如统计失败次数时会用IFNULL查询日志内容时会做日期格式化某些执行器路由列表还会用到字符串拼接。这些都要整理成一张对照表改造时按表逐项替换。用途MySQL 写法Oracle 替换空值兜底IFNULL(name, )NVL(name, )当前时间NOW()SYSDATE当前日期CURDATE()TRUNC(SYSDATE)日期格式化DATE_FORMAT(create_time, %Y-%m-%d %H:%i:%s)TO_CHAR(create_time, YYYY-MM-DD HH24:MI:SS)字符串转日期STR_TO_DATE(2024-01-01, %Y-%m-%d)TO_DATE(2024-01-01, YYYY-MM-DD)列表聚合GROUP_CONCAT(name SEPARATOR ,)LISTAGG(name, ,) WITHIN GROUP (ORDER BY id)条件判断IF(status 1, 成功, 失败)CASE WHEN status 1 THEN 成功 ELSE 失败 END字符串拼接CONCAT(xxl-, job_group)xxl- || job_group这里有一个隐藏问题LISTAGG在 Oracle 旧版本里返回结果不能超过 4000 字节超过会报ORA-01489。如果某张表的执行器路由结果很长一定不要用LISTAGG把大量内容拼成一个字段。稳妥的做法是查出来后在 Java 侧做拼接或者使用 Oracle 12c 的ON OVERFLOW TRUNCATE语法。XXL-Job 管理后台的执行器注册表一般数据量不大但我们还是遇到过一次执行器地址超过 4000 的场景最后把聚合逻辑拉到了 Java 层处理。另外把CONCAT(a, b)改成a || b时要谨慎处理空值。Oracle 里字符串和 NULL 拼接结果还是 NULL容易把原本 MySQL 下能正常显示的文案变成空。如果原 SQL 里有字符串拼接必须对可能为 NULL 的字段做NVL。3.4 定时清理与批量操作改造XXL-Job 默认的日志清理逻辑会根据配置保留天数清理旧日志。MySQL 下很多人会写成DELETE FROM xxl_job_log WHERE trigger_time #{clearBeforeTime} LIMIT 1000;Oracle 不支持DELETE ... LIMIT。我们的替换方案是用 ROWNUM 加子查询分批删除避免一次性删除大量数据撑爆 undo 表空间。一个可参考的写法DELETE FROM xxl_job_log WHERE id IN ( SELECT id FROM ( SELECT id FROM xxl_job_log WHERE trigger_time #{clearBeforeTime} ORDER BY trigger_time ) WHERE ROWNUM 1000 );注意Oracle 的ROWNUM是在查询结果返回前生成的如果内层查询没有稳定的排序每次取出的 1000 条可能不一样。所以内层一定要有ORDER BY并且建议在trigger_time字段上建索引。否则数据量大之后这个清理 SQL 会变成全表扫描调度中心一到凌晨清理日志就会把数据库打满。我们踩过的一个坑是清理任务和调度任务都在同一个连接池里跑日志清理批次大小设得太大导致阻塞了正常任务的写操作。最后我们把每批删除条数控制在 500 到 1000并且让清理任务使用单独的事务每批提交一次。这个思路在 MySQL 下并不明显但在 Oracle 下对锁和 undo 的压力是必须考虑的。3.5 分布式锁与事务边界注意XXL-Job 的调度触发过程用到了xxl_job_lock表核心就是通过SELECT ... FOR UPDATE拿到分布式锁。Oracle 对这个语法是支持的但有两个细节值得注意。第一个是事务边界。SELECT ... FOR UPDATE获得的锁只有在事务提交或回滚时才释放。MyBatis 默认的ExecutorType下如果漏了Transactional锁可能还没提交就被释放调度触发就会产生并发问题。我们在改造时把所有调用锁表的 Service 方法都重新检查了一遍事务注解确保FOR UPDATE和后续的业务更新在同一个事务内。第二个是索引。Oracle 的FOR UPDATE会锁住查询涉及的行。如果xxl_job_lock表的数据量很小影响不大但如果有大量锁记录且没走索引极端情况下容易产生行锁升级或者锁等待。这块的优化思路是确保lock_name上有主键或唯一索引。XXL-Job 默认表结构里lock_name本身就是主键所以算是安全。4. 驱动配置、报错速查与运维细节4.1 驱动依赖与连接池参数配置改造的第一步是替换 JDBC 驱动。Maven 里增加 Oracle 官方驱动依赖版本要和数据库版本匹配。如果数据库是 Oracle 19c使用 JDK 8可以用dependency groupIdcom.oracle.database.jdbc/groupId artifactIdojdbc8/artifactId version19.3.0.0/version scoperuntime/scope /dependency数据源配置从 MySQL 改成 Oracle 时最需要注意的是 URL 格式。老格式的 SID 方式spring.datasource.urljdbc:oracle:thin:192.168.1.100:1521:orcl使用服务名时是新格式spring.datasource.urljdbc:oracle:thin://192.168.1.100:1521/orclpdb spring.datasource.usernamexxl_admin spring.datasource.passwordxxxxxx spring.datasource.driver-class-nameoracle.jdbc.OracleDriver分号和斜杠这两种写法不能混。很多网上资料喜欢用 SID 方式但在多实例、PDB 环境下用服务名更符合容器化和云环境的使用习惯。另外Oracle 的连接数默认参数相对保守调度中心如果并发触发任务较多要适当调大 HikariCP 的maximum-pool-size同时检查数据库端的processes和sessions参数避免出现ORA-12519: TNS:no appropriate service handler found。4.2 常见报错排查速查表改造过程中我们把遇到的报错和解决办法整理成了一张表这里直接分享出来能帮你减少大量试错时间。报错信息可能原因处理方式ORA-00933: SQL command not properly ended还在用LIMIT或 NULL改成OFFSET FETCH或ROWNUM空值判断用IS NULLORA-00942: table or view does not exist表大小写或 Schema 权限问题建表和 SQL 都用大写给应用账号授权ORA-02289: sequence does not exist序列名称大小写不一致或未授权统一使用大写序列名并检查SELECT权限ORA-01489: result of string concatenation is too longLISTAGG超过 4000 字节改成 Java 拼接或分批聚合ORA-01795: maximum number of expressions in a list is 1000IN后的参数超过 1000 个拆成多个批次每个不超过 1000ORA-01407: cannot update column to NULL非空字段更新成 NULL在 SQL 中显式NVL或检查入参ORA-01745: invalid host/bind variable nameMyBatis 参数名非法或重复检查#{}命名和 SQL 占位符Ora-00904: invalid identifier字段名大小写或列不存在确认字段名一致小写字段加双引号最后那个invalid identifier的坑很常见。MySQL 对字段名大小写不敏感但 Oracle 建表时如果不加引号列名默认是大写。如果你的建表脚本里有小写加引号的字段那么后续所有 SQL 都必须带上双引号和小写否则就会报这个错。我们团队在改造时统一约定“建表全部大写、SQL 不写引号”从源头避开这个问题。4.3 字符集、时区与中文乱码XXL-Job 的管理后台会展示任务描述、执行器名字等中文内容如果 Oracle 字符集配置不对很容易出现乱码。MySQL 端很多库用了utf8mb4而 Oracle 中对应的推荐字符集是AL32UTF8。建库前一定要确认否则后面再改字符集非常痛苦。应用侧也要检查 JDBC URL 是否需要追加oracle.jdbc.defaultNChartrue。如果表字段使用的是NVARCHAR2不设置这个参数可能会出现 JDBC 类型转换异常。我们这次统一使用VARCHAR2没有在 URL 里做额外配置中文读写都正常。如果你们的表结构历史原因用了NVARCHAR2建议在连接属性里显式声明避免在不同环境之间出现编码不一致。时区问题也要提前对齐。Oracle 的SYSDATE返回的是数据库服务器本地时间SYSTIMESTAMP返回带时区的时间。XXL-Job 在调度触发时会把触发时间写入日志如果应用服务器和数据库服务器时区不一致日志里的时间就会和业务预期有偏差。这个不是 MySQL 和 Oracle 的语法差异但属于替换数据库后最容易暴露出来的隐藏问题。我们的方案是让数据库和应用统一使用 UTC 或统一定义东八区并且不要在 SQL 里依赖SYSDATE生成业务时间尽量由 Java 侧传入。4.4 日志大表的日常运维XXL-Job 的日志表在任务频率高的情况下增长非常快。MySQL 下很多人靠定时删除或者按天分表来扛Oracle 里更好的方案是范围分区。按触发时间做 Range 分区CREATE TABLE xxl_job_log ( id NUMBER(20) NOT NULL, job_id NUMBER(10), trigger_time TIMESTAMP, ... ) PARTITION BY RANGE (trigger_time) ( PARTITION p_2024_01 VALUES LESS THAN (TO_DATE(2024-02-01, YYYY-MM-DD)), PARTITION p_2024_02 VALUES LESS THAN (TO_DATE(2024-03-01, YYYY-MM-DD)) );分区表的好处是清理旧日志时可以直接DROP PARTITION不需要对整张表做大量删除效果比分批删除好很多。但如果原表已经建成普通表在 Oracle 里在线转换成分区表比较复杂。项目时间允许的话我建议从改造一开始就用分区表设计xxl_job_log给未来留下高枕无忧的余地。另外Oracle 的 undo 表空间和临时表空间要预留充足。XXL-Job 的报表查询可能会对日志表做聚合统计如果临时表空间太小排序操作会频繁报ORA-01652。上线前最好让 DBA 评估一下undo_retention和临时表空间大小不要等监控告警了才处理。5. 回归测试、性能验证与上线落地5.1 功能回归测试清单数据库改造完之后功能回归不能只测“能跑起来”而是要覆盖 XXL-Job 的完整调度链路。我的测试清单大致如下新增、编辑、删除、复制任务检查界面展示和数据库落库。手动执行一次任务检查执行器是否正常接收、调度日志是否有记录。启动、暂停、恢复任务查看触发器状态变化。配置 Cron 任务观察多个时间点触发是否准确。测试失败重试、超时中断、阻塞处理策略。注册、摘除执行器检查执行器管理页面的心跳和地址更新。开启报警邮箱配置触发一个失败任务确认告警逻辑正常。高并发场景下同时触发多个任务观察调度日志是否有丢失或重复。执行日志清理任务查看删除是否分批生效Oracle 侧锁等待是否异常。从管理后台进入报表页面检查统计数据和图表是否正常。这份清单看着基础但每一条都可能在迁移后暴露问题。我们实际执行时发现第 3 条和第 8 条最容易翻车都是因为事务边界没控制好。尤其是在 Oracle 下即使 SQL 都改对了事务隔离级别和连接池配置也会影响调度触发的并发表现。5.2 索引、分区与性能对比回归测试通过后不能直接当上线完成还应该做一轮针对 Oracle 的索引梳理。MySQL 下一些索引在 Oracle 中并不合理比如字段长度偏大、选择性差、函数索引缺失等等。调度日志表的核心查询条件是任务 ID 和触发时间所以至少要保证下面的索引CREATE INDEX idx_xxl_job_log_job_id ON xxl_job_log(job_id); CREATE INDEX idx_xxl_job_log_trigger_time ON xxl_job_log(trigger_time);如果有按状态统计的需求还可以考虑联合索引。Oracle 对索引的维护成本比 MySQL 高一些尤其在频繁写入的日志表上索引数量不是越多越好。一般建议不超过五个否则大批量插入时会放大日志增长和索引维护的开销。性能验证阶段我用一张十万级数据的日志表做了查询对比。改造前 MySQL 的简单分页查询响应在毫秒级改造后 Oracle 在索引缺失的情况下响应一度到秒级。建上trigger_time索引后回到几十毫秒到一百毫秒的范围整体可以接受。但这个结果高度依赖数据量和执行计划建议你们在类似数据规模下做一轮压测不要拿几百条数据测完就认为没问题。5.3 数据迁移与灰度上线如果是从 MySQL 存量环境迁移到 Oracle数据迁移方式要提前规划。简单场景可以停调度后做全量同步复杂场景需要支持双写或准实时增量同步。XXL-Job 的核心表数据量不算大我们当时是停调度、导出 MySQL 数据、按目标 Oracle 表结构做清洗转换后再导入。任务日志表量比较大只迁移了最近三个月再结合分区表自动滚动归档。上线时强烈建议灰度切换。先把 Oracle 环境作为备库或测试环境让少量任务跑几天观察日志写入、报表统计和执行器心跳。确认无误后再把正式域名切过去。切换前要停任务、记录当前调度进度、备份两个库的数据字典并且准备回滚方案。不要直接在生产环境删除 MySQL 的旧库至少保留两周等 Oracle 环境稳定运行后再清理。自动容灾方面也要重新审视。MySQL 时代可能依赖主从复制切到 Oracle 后要确认应用侧的高可用方式是否改变。如果 XXL-Job 调度中心部署了多个实例那么多个实例同时连同一个 Oracle Schema 时序列和锁的并发行为要重新验证。我们当时验证了多节点同时调度确认xxl_job_lock的FOR UPDATE能保证全局唯一调度锁才放心放开。我个人在实际改造中的体会是这次项目的复杂度并不在某个 SQL 本身而在于一堆细节叠加在一起。Oracle 是一个很“规矩”的数据库字段长度、空值、大小写、锁和事务边界都需要主动管理稍微偷懒后面就要返工。最后再分享一个小技巧改造期间把所有 Mapper XML 里涉及 MySQL 关键字的坏味道扫一遍把IFNULL、LIMIT、AUTO_INCREMENT、GROUP_CONCAT全部列出来每解决一个删一行。眼看着清单缩短那种进度感比任何看板都好使。希望这篇记录能帮你们少走几个弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑