Oracle 迁移金仓,50 个存储过程到底要改多少?聊聊实际改造中的几个问题
去年参与过一个 Oracle 到金仓的迁移评估项目。
客户是政务行业,系统已经运行了很多年,数据库里除了业务表,还有不少存储过程、函数和定时任务。
项目刚开始的时候,大家对数据迁移倒不是特别担心。
真正没底的是存储过程。 
Oracle 的 PL/SQL 用了这么多年,很多业务逻辑直接写在数据库里。如果目标数据库不兼容,后面就得安排开发人员逐个改造,再重新做业务测试。
客户一开始也按比较保守的方式估算了工作量,预留了不少开发和测试时间。
后来我们用 KDMS 对数据库对象做了一轮评估。
其中 50 个存储过程的初步评估结果是:
| 评估结果
|
数量
| | --- | --- | |
无需修改即可编译
|
35 个
| |
需要少量调整
|
10 个
| |
涉及较复杂的兼容性问题
|
5 个
|
看到这个结果,大家才发现,实际需要处理的存储过程比原先想象的少。
不过,评估报告出来以后,事情还没有结束。
35 个存储过程能直接编译,并不意味着这 35 个已经完成迁移。
后面还得检查执行结果、事务行为、异常处理,以及应用程序调用是否正常。
这也是我后来做数据库迁移时比较在意的一点:评估报告可以帮我们估算改造量,但不能代替实际验证。
一、为什么存储过程比普通 SQL 更难迁?
普通业务 SQL 相对好处理。
例如:
SQL
ini
SELECT *
FROM orders
WHERE order_id = 10001;
这种查询只要表结构、数据类型和 SQL 语法兼容,通常不会有太大的改造工作量。
但存储过程不一样。
一个运行了很多年的 Oracle 系统,可能会把大量业务逻辑放在 PL/SQL 里。
例如:
SQL
sql
CREATE OR REPLACE PROCEDURE update_order_status (
p_order_id IN NUMBER
)
IS
BEGIN
UPDATE orders
SET status = 'COMPLETED'
WHERE order_id = p_order_id;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/
这还算简单的。
复杂一点的存储过程,里面可能同时包含游标、动态 SQL、异常处理、临时表、事务控制,还会调用其他存储过程或 Oracle 系统包。
迁移的时候,不能只看这些语句能不能编译。
比如原来的过程里有一个异常处理逻辑:
SQL
less
EXCEPTION
WHEN NO_DATA_FOUND THEN
...
目标数据库能识别这个异常,并不代表所有查询场景下都会以相同方式触发它。
又比如事务。
原来的过程内部执行了 COMMIT,但应用程序外层可能还存在事务管理。
数据库换了以后,如果事务边界发生变化,问题就不只是存储过程本身了,还可能影响 Java 业务代码。
所以我现在看存储过程迁移,通常会分成两个问题:
第一,原来的代码能不能继续执行?第二,执行以后的业务行为有没有变化?
后一个问题往往更费时间。
二、50 个存储过程,为什么只有 15 个需要调整?
回到前面那个项目。
50 个存储过程里,有 35 个在初步评估阶段没有发现需要修改的语法问题。
这部分过程主要使用常见的 PL/SQL 结构,例如:
SQL
sql
BEGIN
...
END;
以及游标循环、条件判断、异常处理和普通 DML 操作。
例如:
SQL
sql
CREATE OR REPLACE PROCEDURE update_order_amount
IS
BEGIN
FOR rec IN (
SELECT order_id, amount
FROM orders
WHERE status = 'PENDING'
) LOOP
UPDATE orders
SET amount = rec.amount * 1.05
WHERE order_id = rec.order_id;
END LOOP;
END;
/
这类存储过程的业务逻辑比较简单,也没有调用太多 Oracle 特有的系统功能。
在金仓相应的 Oracle 兼容环境下,迁移工作通常集中在编译检查和业务验证上。
但剩下的 15 个就不太一样了。
其中一部分涉及 SQL 写法差异,另外一些调用了 Oracle 特有的系统包,或者依赖原有数据库的调度、文件访问等功能。
这两类问题,处理方式完全不同。
三、语法不兼容,通常不是最麻烦的
先说比较容易处理的部分。
比如 Oracle 里的 ROWNUM。
原系统可能有这样的查询:
SQL
sql
SELECT *
FROM orders
WHERE ROWNUM <= 10;
如果目标环境不支持相同的写法,可以根据业务需求改成:
SQL
sql
SELECT *
FROM orders
LIMIT 10;
但这里有个容易忽略的问题。
如果原来的 SQL 是:
SQL
sql
SELECT *
FROM orders
WHERE ROWNUM <= 10
ORDER BY order_date DESC;
就不能简单改成:
SQL
sql
SELECT *
FROM orders
ORDER BY order_date DESC
LIMIT 10;
因为两条 SQL 的含义不同。
第一条是在筛选出最多 10 条记录后,对这部分结果排序。
第二条是先排序,再取前 10 条。
如果原来的业务真正想查的是最新 10 条订单,那么第二种写法可能更符合需求。
但这属于业务逻辑确认,而不是简单的语法替换。
数据库迁移时,不能因为新 SQL 看起来更合理,就默认可以改变原来的查询结果。
类似的问题还有 DECODE、NVL、序列调用、外连接等。
这些功能在金仓不同版本和兼容模式下的支持情况可能不同。评估工具如果标记出差异,就需要结合实际 SQL 逐条确认。
不过,相比复杂业务逻辑,这类问题一般比较容易定位。
真正花时间的,往往是那些依赖 Oracle 特有功能的存储过程。
四、碰到 DBMS_JOB、UTL_FILE,先别急着重写
前面提到的 5 个复杂存储过程,主要涉及 Oracle 特有的系统包。
例如:
SQL
DBMS_JOB
用于数据库内部的定时任务。
还有:
SQL
UTL_FILE
用于文件读写。
这类功能迁移时,需要考虑的不只是函数名能不能对上。
比如一个定时任务,原来每天凌晨执行:
SQL
ini
BEGIN
DBMS_JOB.SUBMIT(
job => v_job,
what => 'sync_order_data;',
next_date => SYSDATE,
interval => 'SYSDATE + 1'
);
END;
/
如果迁移到金仓以后,原有调度接口不能按相同方式工作,就得考虑其他实现。
可以先检查目标版本是否提供兼容接口或对应的任务调度能力。
如果没有满足需求的数据库内部调度方案,也可以考虑将任务交给应用层。
例如:
markdown
Spring Boot
↓
定时任务
↓
调用存储过程
↓
执行数据同步
但这种调整不能只改一个调用入口。
还需要确认任务失败以后怎么重试、多个应用实例会不会重复执行、执行记录在哪里保存,以及任务是否允许并发。
如果原来的数据库任务已经运行了很多年,这些行为可能早就被业务系统默认依赖了。
因此,我一般不会看到 DBMS_JOB 就直接判断必须重构。
先确认目标版本的兼容能力,再决定是保留原实现、调整调用方式,还是把调度逻辑迁到应用层。
这样更容易控制改造范围。
五、自治事务是我会单独检查的一项
Oracle 里还有一个比较特殊的功能:
SQL
ini
PRAGMA AUTONOMOUS_TRANSACTION;
典型场景是记录日志。
比如业务事务执行失败,需要回滚,但希望错误日志仍然保留下来。
原来的代码可能类似:
SQL
sql
CREATE OR REPLACE PROCEDURE write_error_log (
p_message IN VARCHAR2
)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO error_log(message, create_time)
VALUES (p_message, SYSDATE);
COMMIT;
END;
/
迁移时,需要检查金仓目标版本及兼容模式对自治事务的支持情况。
即使相关语法可以正常编译,我也会安排单独测试。
例如:
markdown
开启业务事务
↓
执行数据修改
↓
调用日志存储过程
↓
主动触发业务异常
↓
回滚外层事务
↓
检查日志是否保留
如果日志跟着外层事务一起回滚了,就说明迁移后的行为没有满足原来的业务要求。
这种问题只靠编译检查是发现不了的。
所以涉及事务的存储过程,我一般都会额外关注:
-
提交和回滚的边界
-
异常发生后的数据状态
-
锁等待和事务隔离行为
-
与 Java 事务管理的配合
尤其是核心业务系统,事务行为一定要在正式切换前验证清楚。
六、KDMS 的价值,不只是告诉你兼容率有多高
以前评估数据库迁移工作量,最麻烦的是不知道从哪里开始。
假设数据库里有几百个存储过程。
让开发人员一个个打开看,先不说能不能看懂业务,光把所有对象梳理一遍就需要不少时间。
所以这次项目我们先用 KDMS 做评估。
我比较关注的不是报告首页的兼容率,而是具体对象的问题清单。
例如:
| 对象
|
初步评估结果
|
后续处理
| | --- | --- | --- | |
普通订单处理过程
|
无明显语法问题
|
编译及业务验证
| |
报表统计过程
|
存在 SQL 差异
|
修改并核对统计结果
| |
定时同步过程
|
涉及特定系统包
|
验证兼容接口及调度行为
| |
文件导出过程
|
涉及文件操作
|
检查权限、路径及接口
|
有了这个清单,后面的工作就容易安排了。
普通 SQL 差异可以集中处理。
涉及系统包和复杂事务的过程,单独安排开发和测试。
如果某个过程被多个业务模块调用,还需要提前确认它的影响范围。
这比简单地按照"一个存储过程需要半天"去估算工作量靠谱得多。
不过,评估报告仍然只是起点。
有些问题需要运行以后才会暴露,尤其是动态 SQL、外部依赖和特定业务数据触发的异常分支。
七、编译通过以后,我会再做一轮业务验证
这是我觉得迁移项目里最不能省的步骤。
一个存储过程能正常编译,至少说明它在当前环境下没有明显的编译错误。
但上线需要的不是"能编译"。
而是原来的业务还能正常工作。
例如一个订单结算过程:
markdown
读取订单
↓
计算金额
↓
更新订单状态
↓
写入结算记录
↓
更新账户余额
迁移以后,我会重点检查正常执行、重复执行、异常回滚和并发执行等情况。
如果原来的过程还涉及金额计算、日期转换、字符串处理,就需要核对迁移前后的结果。
比较稳妥的方式是准备一组固定测试数据,在 Oracle 和金仓环境中分别执行,然后对比关键业务表的最终状态。
对于核心过程,还需要结合实际数据量测试执行时间。
毕竟同一段 SQL,换了数据库以后,执行计划可能发生变化。
原来 Oracle 上使用的索引和统计信息,也不代表在金仓上一定能得到相同的执行效果。
迁移成功的标准不是存储过程全部变绿,而是业务结果、事务行为和性能都满足上线要求。
八、存储过程的改造量,到底应该怎么算?
回到文章开头的那组数据。
50 个存储过程里,35 个无需修改即可编译,10 个需要少量调整,5 个存在较复杂的兼容性问题。
如果只看数量,会觉得:
70% 不用改,20% 小改,10% 大改。
但如果要估算实际工期,我不会直接按照这个比例分配人力。
因为存储过程之间的复杂度差异太大了。
一个几十行的普通查询过程,可能半小时就能完成改造和测试。
但一个上千行、调用多个系统包、涉及跨表事务的核心结算过程,可能需要几天甚至更长时间。
所以我更倾向于把工作量拆成三个部分:
01
兼容性改造
需要修改多少 SQL、存储过程、函数和外部依赖。
02
业务回归测试
需要覆盖多少业务分支、异常场景和事务逻辑。
03
性能与上线验证
需要优化多少核心 SQL,是否涉及应用代码修改、数据校验和正式割接。
只有把这三部分都考虑进去,估算出来的工期才比较接近实际情况。
尤其对于老系统,很多存储过程虽然代码不复杂,但调用关系不清楚,反而需要花更多时间做回归测试。
最后说几句
这次 Oracle 迁移金仓的评估项目,让我印象比较深的不是 70% 这个数字。
而是评估前后,大家对工作量的认识发生了变化。
最开始觉得存储过程很多,可能要改很久。
后来逐个梳理才发现,大部分过程使用的都是比较常见的 PL/SQL 写法,真正需要重点处理的对象并没有想象中那么多。
但反过来,也不能因为大部分过程可以直接编译,就认为迁移工作已经完成了。
有些问题恰恰藏在那些能正常执行的代码里。
例如事务边界、异常处理、排序结果和隐式类型转换。
这些东西不一定会报错,但可能影响实际业务。
所以如果现在再接一个 Oracle 到金仓的项目,我还是会先做兼容性评估,把存储过程、函数、系统包依赖和调用关系梳理出来。
然后根据实际问题安排改造和测试。
先搞清楚哪些代码需要改,再确认哪些业务行为不能变。
这比一开始就讨论"金仓对 Oracle 的兼容率到底有多高",更能帮助开发团队把迁移项目真正落地。