文章目录
-
- 每日一句正能量
- [1. 背景与问题](#1. 背景与问题)
- [2. 环境与数据](#2. 环境与数据)
-
- [2.1 示例环境](#2.1 示例环境)
- [2.2 对象清单不是"对象数量表"](#2.2 对象清单不是“对象数量表”)
- [2.3 Oracle 源端对象盘点 SQL](#2.3 Oracle 源端对象盘点 SQL)
- [3. 复现过程](#3. 复现过程)
-
- [3.1 Oracle 示例包](#3.1 Oracle 示例包)
- [3.2 风险一:包级变量状态](#3.2 风险一:包级变量状态)
- [3.3 风险二:默认参数与重载](#3.3 风险二:默认参数与重载)
- [3.4 风险三:异常码契约](#3.4 风险三:异常码契约)
- [3.5 风险四:事务边界](#3.5 风险四:事务边界)
- [3.6 风险五:系统包和外部能力](#3.6 风险五:系统包和外部能力)
- [4. 方案实施](#4. 方案实施)
-
- [4.1 第一步:冻结、导出与版本化](#4.1 第一步:冻结、导出与版本化)
- [4.2 第二步:建立兼容画像](#4.2 第二步:建立兼容画像)
- [4.3 第三步:选择改造路线](#4.3 第三步:选择改造路线)
-
- [路线 A:保持包接口,内部逐项改写](#路线 A:保持包接口,内部逐项改写)
- [路线 B:拆包为独立过程与函数](#路线 B:拆包为独立过程与函数)
- [路线 C:数据库逻辑与应用逻辑分层](#路线 C:数据库逻辑与应用逻辑分层)
- [4.4 第四步:消除包状态依赖](#4.4 第四步:消除包状态依赖)
- [4.5 第五步:规范动态 SQL](#4.5 第五步:规范动态 SQL)
- [4.6 第六步:重建权限模型](#4.6 第六步:重建权限模型)
- [4.7 第七步:编译和无效对象治理](#4.7 第七步:编译和无效对象治理)
- [5. 结果对比](#5. 结果对比)
-
- [5.1 编译验证](#5.1 编译验证)
- [5.2 接口契约验证](#5.2 接口契约验证)
- [5.3 单元测试模板](#5.3 单元测试模板)
- [5.4 源端与目标端对照测试](#5.4 源端与目标端对照测试)
- [5.5 数据校验 SQL](#5.5 数据校验 SQL)
- [5.6 性能与并发验证](#5.6 性能与并发验证)
- [5.7 示例结果表](#5.7 示例结果表)
- [6. 风险与复盘](#6. 风险与复盘)
-
- [6.1 灰度切换策略](#6.1 灰度切换策略)
- [6.2 回退方案](#6.2 回退方案)
- [6.3 常见失败教训](#6.3 常见失败教训)
- [6.4 最终复盘](#6.4 最终复盘)

每日一句正能量
遇到小事可以指望别人,遇到大事,千万不能把自个儿的命运拴到别人身上。
小事求助是协作;大事托付是押注。因为大事的后果往往超出他人能够或愿意承担的范围。这不是不信任他人,而是对自己负责------你的核心决策、人生转折、安身立命的根基,必须握在自己手里。依赖他人决定命运,就等于把方向盘交给了乘客。

1. 背景与问题
某核心业务系统长期运行在 Oracle 上,业务规则大量沉淀在数据库端。订单确认、费用计算、账户冻结、批量清算、月末结转等逻辑,并不是简单的单表增删改查,而是由二十余个包、数百个过程和函数共同完成。应用侧只负责传参和接收返回值,因此数据库迁移的难点不在于"表能否导过去",而在于这些存储程序能否保持原有语义。
项目初期,团队曾把"兼容"理解为"DDL 转换后可以编译"。实际验证很快暴露出问题:有些对象虽然编译成功,但包级变量的生命周期发生变化;有些过程在异常分支中提交事务,目标端行为与预期不一致;有些函数依赖 Oracle 系统包、DB Link、目录对象或自治事务;还有一部分重载过程在 JDBC 调用时出现参数匹配歧义。
因此,本次迁移采用四层判断标准:
- 语法兼容:对象是否能创建,依赖是否完整;
- 接口兼容:参数顺序、类型、IN/OUT、默认值和返回值是否一致;
- 语义兼容:事务、异常、空值、包状态、动态 SQL、权限模型是否一致;
- 运行兼容:并发、性能、锁行为、日志、审计和回退是否满足生产要求。
Oracle 的包由包规范和包体组成,可封装相关过程、函数、变量、常量和游标。KingbaseES 提供过程、函数和包等 PL/SQL 能力,但具体兼容范围与行为仍应以目标版本、兼容模式和实测结果为准。迁移工作不能只看功能清单,而应围绕真实调用链逐项验收。
2. 环境与数据
2.1 示例环境
| 项目 | 源端 | 目标端 |
|---|---|---|
| 数据库 | Oracle 19c(示例) | KingbaseES V8/V9(按项目版本填写) |
| 兼容模式 | Oracle 原生 | Oracle 兼容模式 |
| 字符集 | AL32UTF8 | UTF8 |
| 应用 | Java + MyBatis | Java + MyBatis |
| 存储程序规模 | 24 个包、186 个过程、73 个函数 | 待迁移 |
| 核心调用 | 订单确认、计费、清算、月结 | 待验证 |
正式项目中应补充补丁版本、驱动版本、迁移工具版本、参数文件、对象数量、调用频度和峰值并发。尤其是驱动调用存储过程的方式,会直接影响重载解析、OUT 参数读取和游标返回。
2.2 对象清单不是"对象数量表"
真正可用的对象清单至少应包含以下字段:
| 字段 | 说明 |
|---|---|
| owner | 对象所属用户 |
| object_name | 对象名 |
| object_type | PACKAGE、PACKAGE BODY、PROCEDURE、FUNCTION 等 |
| status | VALID / INVALID |
| source_lines | 源码行数 |
| dependency_count | 依赖对象数量 |
| caller_count | 被其他对象或应用调用次数 |
| feature_tags | 动态 SQL、自治事务、系统包、DB Link、文件、邮件等 |
| migration_level | A/B/C/D 级 |
| test_owner | 负责测试的业务人员 |
| rollback_unit | 回退时的最小切换单元 |
迁移分级建议如下:
- A 级:直接迁移。普通 DML、条件分支、循环、简单游标和标准异常处理;
- B 级:轻量改写。函数名、系统视图、日期函数、字符串函数、动态 SQL 写法等需要调整;
- C 级:结构重构。包状态、重载、复杂集合、自定义类型、自治事务、跨库调用;
- D 级:下沉应用层或服务化。文件、邮件、操作系统命令、复杂外部依赖以及难以验证的数据库内编排。
2.3 Oracle 源端对象盘点 SQL
sql
SELECT owner,
object_type,
COUNT(*) AS object_count,
SUM(CASE WHEN status = 'INVALID' THEN 1 ELSE 0 END) AS invalid_count
FROM dba_objects
WHERE owner IN ('BIZ_CORE')
AND object_type IN ('PACKAGE','PACKAGE BODY','PROCEDURE','FUNCTION','TYPE','TYPE BODY')
GROUP BY owner, object_type
ORDER BY object_type;
SELECT owner,
name AS object_name,
type AS object_type,
COUNT(*) AS source_lines
FROM dba_source
WHERE owner = 'BIZ_CORE'
AND type IN ('PACKAGE','PACKAGE BODY','PROCEDURE','FUNCTION')
GROUP BY owner, name, type
ORDER BY source_lines DESC;
SELECT owner,
name,
type,
referenced_owner,
referenced_name,
referenced_type
FROM dba_dependencies
WHERE owner = 'BIZ_CORE'
ORDER BY name, referenced_owner, referenced_name;
SELECT owner,
object_name,
package_name,
overload,
subprogram_id,
argument_name,
position,
sequence,
in_out,
data_type,
data_length,
data_precision,
data_scale,
defaulted
FROM dba_arguments
WHERE owner = 'BIZ_CORE'
ORDER BY package_name, object_name, overload, sequence;
仅靠系统视图仍不够。应用代码中可能通过字符串拼接调用过程,例如 BEGIN PKG_ORDER.CONFIRM_ORDER(?,?,?); END;,也可能由调度平台、报表工具或数据交换程序调用。因此还要扫描代码仓库、调度任务和运行日志,建立"对象---调用者---业务场景"三方映射。

3. 复现过程
下面选择一个常见的订单处理包,演示"能编译但不等于语义一致"的几个风险点。
3.1 Oracle 示例包
sql
CREATE OR REPLACE PACKAGE pkg_order AS
g_batch_no NUMBER := 0;
PROCEDURE confirm_order(
p_order_id IN NUMBER,
p_operator IN VARCHAR2,
p_result OUT VARCHAR2);
FUNCTION calc_fee(
p_amount IN NUMBER,
p_rate IN NUMBER DEFAULT 0.006)
RETURN NUMBER;
END pkg_order;
/
CREATE OR REPLACE PACKAGE BODY pkg_order AS
FUNCTION calc_fee(
p_amount IN NUMBER,
p_rate IN NUMBER DEFAULT 0.006)
RETURN NUMBER
IS
BEGIN
RETURN ROUND(p_amount * p_rate, 2);
END;
PROCEDURE confirm_order(
p_order_id IN NUMBER,
p_operator IN VARCHAR2,
p_result OUT VARCHAR2)
IS
v_status VARCHAR2(20);
v_fee NUMBER(18,2);
BEGIN
g_batch_no := g_batch_no + 1;
SELECT status, calc_fee(pay_amount)
INTO v_status, v_fee
FROM biz_order
WHERE order_id = p_order_id
FOR UPDATE;
IF v_status <> 'INIT' THEN
RAISE_APPLICATION_ERROR(-20001, '订单状态不允许确认');
END IF;
UPDATE biz_order
SET status = 'CONFIRMED',
fee_amount = v_fee,
operator_id = p_operator,
update_time = SYSDATE
WHERE order_id = p_order_id;
INSERT INTO biz_order_log(
log_id, order_id, action_code, batch_no, create_time)
VALUES(
seq_order_log.NEXTVAL, p_order_id, 'CONFIRM',
g_batch_no, SYSDATE);
p_result := 'SUCCESS';
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_result := 'NOT_FOUND';
WHEN OTHERS THEN
p_result := 'ERROR:' || SQLCODE || ':' || SQLERRM;
RAISE;
END;
END pkg_order;
/
3.2 风险一:包级变量状态
g_batch_no 是包级变量。Oracle 包状态通常与会话相关,连接池复用会话时,它可能持续存在。若迁移后直接保留这种设计,就必须验证目标端会话生命周期、连接池重置策略和异常后的状态变化。
这类变量如果被业务当成"全局递增号",本身就存在设计缺陷:多个会话各有状态,连接断开后状态消失,不能替代序列。改造时应先判断它究竟是缓存、会话上下文、批次号还是错误地承担了持久化职责。
3.3 风险二:默认参数与重载
Oracle 包常用默认参数和重载简化调用。目标端即使支持类似语法,也应验证:
- 只传一个参数时能否命中正确子程序;
- JDBC CallableStatement 按位置绑定和按名称绑定是否一致;
- NULL 实参能否区分
NUMBER与VARCHAR2重载; - 应用升级前后是否使用了不同签名;
- 默认参数表达式是否引用了包变量或函数。
3.4 风险三:异常码契约
应用可能并不只看"成功或失败",而是依赖 SQLCODE、自定义错误码或错误文本。迁移后若把异常全部改成通用异常,虽然事务会回滚,但上层状态机可能无法识别"订单不存在""状态冲突""余额不足"等业务分支。
| Oracle 异常 | 业务含义 | 目标端处理 |
|---|---|---|
NO_DATA_FOUND |
订单不存在 | 返回 NOT_FOUND 或统一业务码 |
-20001 |
状态不允许 | 映射为固定业务错误码 |
| 唯一约束异常 | 幂等冲突 | 返回已处理,不重复执行 |
| 其他异常 | 未知失败 | 记录上下文并回滚 |
3.5 风险四:事务边界
需要重点扫描过程内的 COMMIT、ROLLBACK、保存点和自治事务。数据库过程自行提交,会破坏应用层统一事务;自治事务常用于日志,但也可能导致主事务回滚后日志仍保留。迁移时不能简单删除,也不能机械保留,应明确每个提交点的业务理由。
sql
SELECT owner, name, type, line, text
FROM dba_source
WHERE owner = 'BIZ_CORE'
AND (
UPPER(text) LIKE '%COMMIT%'
OR UPPER(text) LIKE '%ROLLBACK%'
OR UPPER(text) LIKE '%SAVEPOINT%'
OR UPPER(text) LIKE '%PRAGMA AUTONOMOUS_TRANSACTION%'
)
ORDER BY name, type, line;
3.6 风险五:系统包和外部能力
常见依赖包括 DBMS_SCHEDULER、DBMS_OUTPUT、UTL_FILE、UTL_HTTP、DBMS_LOB、DBMS_SQL、DBMS_LOCK、DBMS_RANDOM、邮件包、目录对象和 DB Link。兼容文档即使标记"支持",也应进一步验证参数、权限、安全策略和返回行为。外部网络、文件和系统命令还涉及生产安全审批,通常不宜原样搬迁。
4. 方案实施
4.1 第一步:冻结、导出与版本化
在正式改造前冻结 PL/SQL 对象变更窗口,并把源端原始 DDL、转换后的 DDL、人工改造脚本、依赖与权限脚本、编译日志、测试数据、期望结果和回退脚本纳入版本库。
text
pkg_order/
├── 00_oracle_original.sql
├── 10_kingbase_converted.sql
├── 20_kingbase_refactored.sql
├── 30_grants.sql
├── 40_unit_test.sql
├── 50_regression_test.sql
├── 60_rollback.sql
└── README.md
4.2 第二步:建立兼容画像
sql
SELECT name,
type,
SUM(CASE WHEN UPPER(text) LIKE '%EXECUTE IMMEDIATE%' THEN 1 ELSE 0 END) AS dynamic_sql_hits,
SUM(CASE WHEN UPPER(text) LIKE '%PRAGMA AUTONOMOUS_TRANSACTION%' THEN 1 ELSE 0 END) AS autonomous_hits,
SUM(CASE WHEN UPPER(text) LIKE '%DBMS_%' THEN 1 ELSE 0 END) AS dbms_hits,
SUM(CASE WHEN UPPER(text) LIKE '%UTL_%' THEN 1 ELSE 0 END) AS utl_hits,
SUM(CASE WHEN UPPER(text) LIKE '%@%' THEN 1 ELSE 0 END) AS dblink_hits
FROM dba_source
WHERE owner = 'BIZ_CORE'
GROUP BY name, type
ORDER BY autonomous_hits DESC, dbms_hits DESC, dynamic_sql_hits DESC;
关键词只能做初筛,不能代替人工审查。例如 @ 可能出现在邮件地址或注释中,COMMIT 可能只在注释里。最终分级要结合源码、调用链和生产日志。
4.3 第三步:选择改造路线
路线 A:保持包接口,内部逐项改写
适用于应用大量依赖包名和过程签名、短期不便修改应用的系统。优点是调用侧改动小,缺点是需要严格验证包语义。
sql
\set SQLTERM /
CREATE OR REPLACE PACKAGE pkg_order AS
PROCEDURE confirm_order(
p_order_id IN NUMERIC,
p_operator IN VARCHAR2,
p_result OUT VARCHAR2);
FUNCTION calc_fee(
p_amount IN NUMERIC,
p_rate IN NUMERIC DEFAULT 0.006)
RETURN NUMERIC;
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_order AS
FUNCTION calc_fee(
p_amount IN NUMERIC,
p_rate IN NUMERIC DEFAULT 0.006)
RETURN NUMERIC
IS
BEGIN
RETURN ROUND(p_amount * p_rate, 2);
END;
PROCEDURE confirm_order(
p_order_id IN NUMERIC,
p_operator IN VARCHAR2,
p_result OUT VARCHAR2)
IS
v_status VARCHAR2(20);
v_fee NUMERIC(18,2);
BEGIN
SELECT status, calc_fee(pay_amount)
INTO v_status, v_fee
FROM biz_order
WHERE order_id = p_order_id
FOR UPDATE;
IF v_status <> 'INIT' THEN
RAISE_APPLICATION_ERROR(-20001, '订单状态不允许确认');
END IF;
UPDATE biz_order
SET status = 'CONFIRMED',
fee_amount = v_fee,
operator_id = p_operator,
update_time = CURRENT_TIMESTAMP
WHERE order_id = p_order_id;
INSERT INTO biz_order_log(
log_id, order_id, action_code, create_time)
VALUES(
NEXTVAL('seq_order_log'), p_order_id,
'CONFIRM', CURRENT_TIMESTAMP);
p_result := 'SUCCESS';
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_result := 'NOT_FOUND';
WHEN OTHERS THEN
p_result := 'ERROR';
RAISE;
END;
END;
/
\set SQLTERM ;
上例只展示改造思路。NEXTVAL 写法、异常函数、时间函数和包语法应以实际 KingbaseES 版本为准,并在测试库中确认。
路线 B:拆包为独立过程与函数
适用于包状态无实际价值、对象耦合不高的系统。把公共函数和过程拆成独立对象,包名通过同义层、适配层或应用映射保留。优点是依赖清晰、部署粒度更小;缺点是需要调整调用入口。
路线 C:数据库逻辑与应用逻辑分层
适用于包中混合了文件、网络、任务编排、消息发送和复杂跨系统调用的情况。将数据一致性强、贴近 SQL 的逻辑留在数据库;把外部 I/O、长流程、重试和编排移到应用服务。这样更利于观测、限流和故障隔离。
4.4 第四步:消除包状态依赖
| 原用途 | 建议替代 |
|---|---|
| 唯一编号 | 序列或专用号段服务 |
| 用户上下文 | 应用显式传参、会话上下文表 |
| 缓存字典 | 只读表、物化视图或应用缓存 |
| 批次状态 | 持久化批次表 |
| 临时集合 | 局部变量、临时表或显式集合参数 |
不要在未理解用途前直接删除包变量。对会话状态敏感的过程,应增加"同一连接连续调用"和"连接池复用后调用"两类测试。
4.5 第五步:规范动态 SQL
sql
-- 不推荐:值直接拼接
v_sql := 'UPDATE biz_order SET status = ''' || p_status ||
''' WHERE order_id = ' || p_order_id;
-- 推荐:值使用绑定变量
v_sql := 'UPDATE biz_order SET status = :1 WHERE order_id = :2';
EXECUTE IMMEDIATE v_sql USING p_status, p_order_id;
动态对象名无法直接绑定时,必须从固定白名单映射,不接受外部原始字符串。
4.6 第六步:重建权限模型
Oracle 中 AUTHID DEFINER 与 AUTHID CURRENT_USER 会影响对象以定义者权限还是调用者权限执行。迁移时应梳理对象所有者、应用账号直接权限、角色权限、动态 SQL 权限、跨模式访问、同义词与搜索路径。权限问题常表现为"管理员测试正常,应用账号执行失败",所以所有回归用例必须使用真实应用账号执行一次。
4.7 第七步:编译和无效对象治理
目标端部署后先统计无效对象,再按依赖顺序修复。不能只看最终无效对象数量,还要保存每次编译错误的对象名、行号、错误码和修复动作。
| issue_id | object_name | object_type | error_stage | error_message | root_cause | action | owner | status |
|---|
5. 结果对比

5.1 编译验证
sql
SELECT object_type,
COUNT(*) AS object_count,
SUM(CASE WHEN status <> 'VALID' THEN 1 ELSE 0 END) AS invalid_count
FROM user_objects
WHERE object_type IN ('PACKAGE','PACKAGE BODY','PROCEDURE','FUNCTION')
GROUP BY object_type;
编译通过率应达到 100%,但它只是第一道门槛。
5.2 接口契约验证
为每个过程生成接口快照,校验参数名称与顺序、IN/OUT、数据类型、精度、长度、默认参数、重载编号、返回类型、游标列顺序和业务错误码。建议在源端和目标端分别导出 JSON 或 CSV,再做差异比较。
5.3 单元测试模板
sql
INSERT INTO biz_order(
order_id, status, pay_amount, fee_amount, operator_id, update_time)
VALUES(
900001, 'INIT', 1000.00, NULL, NULL, CURRENT_TIMESTAMP);
DECLARE
v_result VARCHAR2(100);
BEGIN
pkg_order.confirm_order(
p_order_id => 900001,
p_operator => 'tester',
p_result => v_result);
DBMS_OUTPUT.PUT_LINE(v_result);
END;
/
SELECT order_id,
status,
pay_amount,
fee_amount,
operator_id
FROM biz_order
WHERE order_id = 900001;
单元测试必须覆盖正常、边界、异常、重复调用、并发调用、调用后回滚、应用账号权限、中文与 NULL 参数、连接池复用后的状态。
5.4 源端与目标端对照测试
对同一输入分别调用 Oracle 和 KingbaseES,保存输入、返回码、OUT 参数、数据变化、错误信息和执行耗时。对于写操作,可使用隔离数据、事务回滚、影子表或脱敏快照,避免双写污染。
5.5 数据校验 SQL
sql
SELECT status, COUNT(*)
FROM biz_order
GROUP BY status
ORDER BY status;
SELECT COUNT(*) AS row_count,
SUM(pay_amount) AS total_pay,
SUM(fee_amount) AS total_fee,
MIN(update_time) AS min_time,
MAX(update_time) AS max_time
FROM biz_order;
SELECT o.order_id
FROM biz_order o
LEFT JOIN biz_order_log l
ON l.order_id = o.order_id
AND l.action_code = 'CONFIRM'
WHERE o.status = 'CONFIRMED'
GROUP BY o.order_id
HAVING COUNT(l.log_id) = 0;
SELECT order_id, action_code, COUNT(*)
FROM biz_order_log
GROUP BY order_id, action_code
HAVING COUNT(*) > 1;
对财务和结算过程,还应校验分录借贷平衡、账户余额、日汇总、批次总额和业务日期边界。
5.6 性能与并发验证
记录平均、P95、P99 耗时、每秒调用次数、锁等待、死锁、CPU、I/O、临时空间、批量提交大小、异常率和超时率。不能只比较单次空载耗时,至少要在接近生产数据规模和并发度下压测核心过程。
5.7 示例结果表
| 指标 | Oracle 基线 | KingbaseES 改造后 | 结论 |
|---|---|---|---|
| 对象编译通过率 | 100% | 100% | 达标 |
| 核心接口一致率 | 100% | 100% | 达标 |
| 业务用例通过率 | 100% | 99.8% | 2 个异常码需修正 |
| 数据汇总一致率 | 100% | 100% | 达标 |
| P95 耗时 | 82 ms | 88 ms | 可接受 |
| 并发错误率 | 0.02% | 0.03% | 可接受 |
| 回退耗时 | --- | 12 分钟 | 达标 |
以上数字仅为演示格式,正式文章应替换为真实执行日志、测试截图和问题修复前后对比。
6. 风险与复盘

6.1 灰度切换策略
建议将存储程序切换拆成只读函数、查询型过程、低风险写过程、核心交易过程、批处理与月结过程。灰度阶段通过调用路由控制流量。对于只读函数,可以进行影子调用;对于写过程,可采用隔离账号、影子表、回滚事务或脱敏数据演练。
切换门槛应包含无效对象为 0、核心用例 100% 通过、业务汇总一致、异常码映射完成、P95/P99 在阈值内、无新增死锁、应用账号权限验证通过、回退演练完成、监控和审计可用。
6.2 回退方案
回退不是只修改连接串。写过程切换后,目标端可能已经产生新订单、日志、批次和序列值。完整回退至少包括:
- 停止新请求进入目标端;
- 等待在途事务结束或强制终止;
- 导出切换窗口内的增量数据;
- 对账订单、金额、状态、日志和序列;
- 将可回放增量按业务顺序写回 Oracle;
- 恢复应用调用路由;
- 验证源端对象、权限、序列和任务;
- 保留目标端现场用于问题分析。
为降低回退难度,可在灰度窗口内采用独立业务号段、写入切换批次号,并记录每次过程调用的请求标识。回放脚本必须幂等,重复执行不能生成重复订单或重复分录。
6.3 常见失败教训
过度相信自动转换。 自动工具适合处理大量重复语法,但无法判断包变量是否承担业务状态,也无法理解一次 COMMIT 的业务含义。
只测正常路径。 真正容易出现差异的是异常、边界、并发和回滚路径。
忽视应用调用方式。 数据库控制台手工调用成功,不代表 JDBC、MyBatis、.NET 或调度平台调用成功。
把兼容模式当作完全等价。 兼容模式能降低改造量,但不能消除版本、参数、驱动和实现细节差异。
没有保留源端基线。 若没有输入、输出、错误码、数据变化和耗时基线,出现差异后很难定位。
6.4 最终复盘
包、过程与函数迁移的核心,不是把 PL/SQL 源码换一种语法重新编译,而是把数据库端接口、状态、事务和异常重新做一次工程化验收。最稳妥的路线是先建立对象与调用清单,再按兼容风险分级;低风险对象直接迁移,高风险对象专项改造;用源端基线驱动接口、数据、异常和性能对照;最后以灰度、门禁和可逆回退完成切换。
一套真正可交付的迁移方案,至少应留下对象清单、问题清单、改造脚本、测试证据、性能数据、上线步骤和回退记录。只有这些证据齐全,才能把"代码迁过去了"升级为"业务可以安全运行"。
转载自:https://blog.csdn.net/u014727709/article/details/163173281
欢迎 👍点赞✍评论⭐收藏,欢迎指正