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. 涉及财务、库存、审批类的判断逻辑,这两类问题都不会报错,只会悄悄改变结果,验收时间再紧也不能省这两项检查。
相关推荐
蓝速科技3 分钟前
信创终端 POC 测试实战与选型避坑指南丨蓝速科技
运维·数据库·人工智能·科技·自然语言处理
风哥2号18 分钟前
数据库教程FGMT29‑Linux平台MySQL5.7安装配置与管理入门
数据库
杨云龙UP1 小时前
MySQL Host is blocked because of many connection errors 导致 JDBC 连接失败排查与解决
linux·运维·网络·数据库·sql·mysql·登录失败
这个DBA有点耶2 小时前
数据库数据同步解决方案怎么选?6款主流工具横向对比与信创选型指南
数据库·架构·dba
刃神太酷啦2 小时前
Redis 核心进阶:哨兵、集群、缓存问题与分布式锁详解----《Hello Redis!》(6)
linux·c语言·数据库·c++·redis·分布式·缓存
小雷信息医学2 小时前
不会写复杂代码也能发 SCI?手把手教你用 InSpireR 交互系统一键提取、合并与导出临床科研宽表【第三章】
数据库
旺仔不是程序员3 小时前
复合索引最左前缀原则:PostgreSQL 的 WHERE 为什么必须命中第一列
数据库·后端·sql
旺仔不是程序员3 小时前
字段类型不一致:PostgreSQL 报错与索引失效的第一元凶
数据库·后端·sql
白远山3 小时前
货运跑腿搬家平台开发实战:从需求分析到落地部署指南
数据库·数据挖掘·需求分析
geovindu3 小时前
sql: Data Modeling Patterns
数据库·sql·设计模式·sqlserver