Oracle 迁移评估时,我把这两类问题列在了检查清单最前面

接手一个 Oracle 迁移评估任务时,大家习惯先看语法兼容性:这个存储过程能不能跑、这个函数有没有对应实现、这张视图的写法要不要改。这些问题好查,工具能扫,跑一遍报错日志就知道改哪。真正让人头疼的不是这些------是那些语法完全合法、迁移后照样能跑通、只是结果跟原来不一样的地方。这类问题不报错,不会出现在任何迁移报告的"失败项"里,只会在业务侧发现报表对不上、对账差了几行的时候才暴露出来,而且往往已经是上线之后了。

这次评估里踩到两个这样的问题,一个是外连接消除,一个是空字符串到底算不算 NULL。前者排第一,因为老系统里到处都是 (+) 写法的历史 SQL,谁都说不清当初是不是故意要保留左表全量;后者第二,因为它牵扯到"KES 跟 Oracle 到底像不像"这个问题本身没有固定答案,得看配置。环境是 KES V009R001C010,业务账号连接:

bash 复制代码
ksql -h 127.0.0.1 -p 54321 -U app_user -d app_db

先搭场景:客户表和客户等级表

sql 复制代码
create table app_schema.t_mig_customer (
  cust_id integer primary key,
  cust_name varchar(50) not null,
  grade_code varchar(10)
);

create table app_schema.t_mig_grade (
  grade_code varchar(10) primary key,
  grade_name varchar(30) not null,
  grade_level integer not null
);

insert into app_schema.t_mig_customer(cust_id, cust_name, grade_code) values
  (1, 'customer-a', 'G1'),
  (2, 'customer-b', 'G2'),
  (3, 'customer-c', null),
  (4, 'customer-d', 'G3');

insert into app_schema.t_mig_grade(grade_code, grade_name, grade_level) values
  ('G1', 'silver', 1),
  ('G2', 'gold',   2);

customer-c 从来没定过级,grade_code 是 NULL;customer-dgrade_codeG3,但等级表里压根没有 G3 这一级------这是很多老系统的常态,等级表维护跟不上业务扩张,历史数据里留着一堆对不上号的外键值。查"所有客户"的场景下,这两个客户理论上都应该被保留,只是等级信息显示为空。

陷阱一:(+) 语法本身没毛病,加个条件就变味了

先确认 KES 对 Oracle 这个老写法的兼容程度。(+) 标在哪一边,哪一边就是外连接里可以为空的一侧,最基础的写法:

sql 复制代码
select c.cust_id, c.cust_name, g.grade_name
from app_schema.t_mig_customer c, app_schema.t_mig_grade g
where c.grade_code = g.grade_code(+);

四个客户全部返回,customer-ccustomer-dgrade_name 是空。这一步没什么好说的,(+) 语法接得很到位,老代码原样搬过来完全能用,这也是 KES 主打 Oracle 兼容的地方确实兑现了。

麻烦出在老系统里更常见的写法上:关联条件之外,业务上往往还要再加一层过滤,比如"只看正式定级(等级值大于等于 1)的客户":

sql 复制代码
select c.cust_id, c.cust_name, g.grade_name
from app_schema.t_mig_customer c, app_schema.t_mig_grade g
where c.grade_code = g.grade_code(+)
  and g.grade_level >= 1;

只返回 2 行,customer-acustomer-bcustomer-ccustomer-d 不见了------这条 SQL 里明明写着 (+),摆明了想要外连接的语义,结果却只剩下内连接才会有的行数。

跑一下执行计划确认这背后发生了什么:

sql 复制代码
explain analyze
select c.cust_id, c.cust_name, g.grade_name
from app_schema.t_mig_customer c, app_schema.t_mig_grade g
where c.grade_code = g.grade_code(+)
  and g.grade_level >= 1;

Hash Join,没有 Left 或者 Right 字样,纯内连接。原因不难理解:g.grade_level >= 1 这个条件是加在右表上的,customer-ccustomer-d 对应的 g.grade_level 是 NULL,NULL >= 1 的结果是"未知",在 WHERE 里等于被扔掉。既然这些因为外连接才该保留的行反正都会被这个条件滤掉,"外连接 + 这个过滤"和"内连接 + 这个过滤"结果完全一样,优化器就选了开销更小的内连接去跑------这就是外连接消除,跟 (+) 写没写没关系,(+) 只是外连接的一种写法,底层判断逻辑跟 ANSI 的 LEFT JOIN 是同一套。

为了确认这一点,把同样的逻辑换成 ANSI 写法再跑一遍,作为迁移验收阶段常用的排查方式:

sql 复制代码
explain analyze
select c.cust_id, c.cust_name, g.grade_name
from app_schema.t_mig_customer c
left join app_schema.t_mig_grade g
  on c.grade_code = g.grade_code
where g.grade_level >= 1;

同样是 Hash Join,同样没有 Left 字样,跟 (+) 写法的表现完全一致。这也提醒做迁移验收的人:不管老系统用的是 (+) 还是转译后的 LEFT JOIN,只要 WHERE 里对右表字段做了这种会把 NULL 判定为假的过滤,这个坑就跑不掉,光看语法转译对不对没用,得拿执行计划和行数说话。

修复办法是把这个过滤条件挪进关联条件里:

sql 复制代码
select c.cust_id, c.cust_name, g.grade_name
from app_schema.t_mig_customer c
left join app_schema.t_mig_grade g
  on c.grade_code = g.grade_code
  and g.grade_level >= 1;

四行全部回来,customer-ccustomer-d 的等级字段照常显示为空。这不是换个位置随便试出来的,ON 决定的是"怎么连",先按条件筛一遍右表再去外连接;WHERE 决定的是"连完之后再筛掉什么",条件放这儿就已经是连接做完之后的事了,NULL 行躲不掉。

迁移验收阶段发现这类问题没有捷径:对每一条涉及外连接的存量 SQL,迁移前后的行数必须逐条比对,光看"能不能跑通"是不够的。

陷阱二:空字符串算不算 NULL,得看这一套环境怎么配置

这一节动笔之前,先把两件事确认清楚,不能凭"KES 内核衍生自 PostgreSQL"这种直觉去猜:

sql 复制代码
show database_mode;
show ora_input_emptystr_isnull;

database_modeoracleora_input_emptystr_isnullon。这套环境默认走的是 Oracle 兼容模式------很多人下意识觉得"内核是 PG 系的,行为应该跟 PG 一致",这个假设在这里是不成立的,还是要先确定兼容模式,除非建库时特意指定成 PG 模式。而 ora_input_emptystr_isnull 这个参数,顾名思义就是用来复刻 Oracle 那个经典行为:把长度为零的字符串当成 NULL 处理。

实测验证一下:

sql 复制代码
insert into app_schema.t_mig_customer(cust_id, cust_name, grade_code)
values (5, 'customer-e', '');
sql 复制代码
select
  cust_id, cust_name, grade_code,
  grade_code is null as is_null_flag,
  length(grade_code) as code_len
from app_schema.t_mig_customer
where cust_id = 5;

插入的空字符串 '',查出来 is_null_flagt------被当成 NULL 存了,code_len 也是空白,不是 0。这跟很多人预想的"PostgreSQL 体系不会这样"正好相反,KES 在 Oracle 兼容模式下把 Oracle 这个特有行为原样复刻了下来。

对迁移读者来说,这其实是个好消息:如果老系统的 PL/SQL 或者业务代码依赖了"空字符串等于 NULL"这个假设(比如用 col = '' 判断"未填写"),只要迁移后的 KES 还在 Oracle 兼容模式下,这批代码不用改,行为跟原来一致。真正要小心的是反过来的情况------如果运维后续把数据库切换成 PG 兼容模式,或者手动关掉了这个参数,这批老代码会在没有任何报错的情况下悄悄失效。这也是这个坑最值得记住的地方:它不是一个写死的结论,是随兼容模式和参数走的,迁移评估阶段必须在目标环境里把这两项亲自确认一遍,不能凭经验预判。

迁移避坑清单

  1. 对所有涉及外连接(LEFT/RIGHT JOIN(+))的存量 SQL 做台账登记,迁移前先跑一遍拿到基准行数。
  2. 迁移后同样的 SQL 在 KES 上重跑,行数逐条比对,光凭"能不能跑通"判断不了对错。
  3. 审查 WHERE 子句里是否有对右表(Nullable 一侧)字段的过滤,且没有对称写 (+) 或没有下推到 ON
  4. 迁移前先确认目标环境的 database_modeora_input_emptystr_isnull,把"空字符串算不算 NULL"这件事钉死,再决定这部分代码要不要改。
  5. 涉及财务、库存、审批类的判断逻辑,这两类问题都不会报错,只会悄悄改变结果,验收时间再紧也不能省这两项检查。
相关推荐
Database_Cool_1 小时前
数据库性能不够用,升级到什么方案好?阿里云 PolarDB(云原生数据库领导者,兼容 MySQL/PostgreSQL/Oracle)平滑升级与百倍弹性
数据库·阿里云·云原生
程序猿秃头之路1 小时前
DDD 系列:聚合和聚合根详解
数据库·oracle·ddd·领域驱动设计
码上上班2 小时前
Mongodb课程
数据库·mongodb
农村小镇哥2 小时前
Oracle中的锁和10704对高级队列锁
数据库·oracle
峥无2 小时前
SQL性能调优:从执行计划看懂索引的使用与失效
数据库·sql
xyj29175964113 小时前
在Trae中配置数据库MCP
数据库·ai·mcp
廋到被风吹走3 小时前
【AI】【Claude】从 Prompt 到 Agent 的完整进阶地图
数据库·人工智能·prompt
数智化管理手记3 小时前
智能财务能替代人工核算吗?智能财务核心功能包含哪些?
大数据·网络·数据库·数据挖掘·精益工程
金智维科技官方3 小时前
DeepSeek接入企业系统:流程自动化新玩法
大数据·数据库·人工智能