Oracle迁移国产化KES避坑:盘点极易翻车的隐性SQL逻辑陷阱

前言
现在政务、金融、能源各类项目,都在把Oracle数据库往国产KES上迁移。很多团队做迁移的时候,只会盯着那些直接报语法错误的内容,像字段类型不匹配、自定义函数不存在这类问题。还有一类隐藏问题,大家基本都会漏掉。
这类坑有个特点,代码跑起来不会直接抛错,测试环境有时候数据看着正常,一上生产就时不时出现统计数据错乱。
问题根源不在语法不兼容,是Oracle和KES底层执行引擎、优化处理逻辑本身就不一样。开发写SQL的时候,长期靠着Oracle独有的执行特性写代码,换到KES之后整套逻辑直接跑偏。
下面我分六类高频隐性问题,搭配可复现代码、背后逻辑、整改写法来讲,做迁移、写代码评审的时候都能直接拿来用。

一、头号高危坑:WHERE里带修改变量的函数,执行顺序完全不一样

1. 原来Oracle里经常有人这么写

SELECT * FROM business_order
WHERE order_no = pkg_param.get_cur_no()
AND pkg_param.set_cur_no('20260720') = 1;

2. Oracle、KES两边执行逻辑有差别

先说Oracle这边的情况。AND连接的条件,数据库不会固定从左往右跑,优化器会根据开销调整执行顺序。
优化器把右边赋值语句先执行,数据就能正常查;要是先执行左边读取函数,变量还没赋值,结果就是空。
同一段SQL,不同执行计划出来的数据还不一样,测试环境刚好碰到先赋值的执行路径,看起来没问题,线上换个计划直接出故障。

再看KES这边,规则定得很清楚,WHERE里并列条件严格按照书写的先后顺序执行,不会随便调换。
上面这条语句永远先执行get_cur_no,变量是空值,过滤之后查出来永远是空数据,一上线业务直接没法用。

3. 这个问题带来的风险

第一是会话变量互相干扰。包里面全局变量的生命周期和数据库会话绑定,连接池把会话回收之后,上次执行残留的值还在。下次新业务拿到会话,就会读出不属于自己的数据,线上故障很难复现排查。
第二是执行计划不受控。SQL本身只是用来查数据,标准里从来没有规定多条件的执行顺序,不管是升级数据库版本,还是统计数据变化,都有可能改动执行顺序,属于长期隐藏隐患。

4. 标准整改写法,必须严格遵守规范

任何WHERE、JOIN、SELECT里面,都不能放修改变量、改动数据的函数。
正确拆分执行步骤:
-- 第一步单独调用赋值存储过程
CALL pkg_param.set_cur_no('20260720');
-- 第二步单独执行查询语句
SELECT * FROM business_order WHERE order_no = pkg_param.get_cur_no();
如果是单纯读取、不修改内容的函数,在KES里可以标记稳定属性,优化器处理更友好:

CREATE OR REPLACE FUNCTION pkg_param.get_cur_no()
RETURNS VARCHAR STABLE AS $$ ... $$ LANGUAGE PLPGSQL;

二、隐式类型转换带来索引失效、匹配规则不一致

1. 现场复现场景

Oracle表user_info,phone字段是VARCHAR2字符串类型。
很多开发会直接写数字去匹配,像下面这样:

SELECT * FROM user_info WHERE phone = 13800138000;

Oracle处理逻辑:会把phone字段转成数字再比对,索引能正常走。但如果手机号前面带0,转换之后前导0会消失,匹配数据出错。
KES处理逻辑:反过来把数字常量转成字符串比对,表面结果看着一致。但碰到NULL、空字符串、超长数字的时候,两边判断逻辑不一样。
更大的麻烦是,字段建了B树索引,发生隐式转换之后,索引直接用不上,千万级大表直接全表扫描,查询超时。

2. 迁移整改要求

  1. 等值匹配的时候,字段和常量类型必须完全一致,字符串常量统一加单引号;
  2. 迁移之前扫描所有业务SQL,删掉字符串等于数字、日期等于字符串这类写法;
  3. 存量历史数据统一清洗,避免Oracle遗留脏数据造成匹配异常。

三、多表JOIN关联顺序差异,容易漏数据

1. 问题产生原因
Oracle优化器会根据统计行数、索引情况,自动选小表当驱动表。KES统计信息、成本计算逻辑和Oracle不一样,多张表关联的时候,驱动表很容易被调换。
举个常见写法:

SELECT a.* FROM order_list a
LEFT JOIN pay_record b ON a.order_id = b.order_id
WHERE b.pay_status = 1;

Oracle大多会直接转换成内连接;KES在部分配置下会先左连接再过滤,两边最终查到的行数对不上。

2. 隐藏风险

测试库数据量不大,两种执行方式耗时差别很小。等到线上千万条数据,问题就暴露了:
查询耗时从毫秒涨到几分钟;一对多关联场景,还会多出重复行,或者丢失业务记录。

3. 落地处理办法

  1. 多表关联需求,可以用Hint固定表关联顺序,和原Oracle执行逻辑对齐;
  2. 迁移完成后,对每张表执行ANALYZE,采集完整统计信息,防止优化器误判;
  3. 如果业务本身就是只需要匹配到的数据,直接把LEFT JOIN改成INNER JOIN,不要写左连接再加右表过滤

四、NULL和空字符串判断规则不一样

1 Oracle原有逻辑

Oracle里面没有真正的空字符串,插入’‘会自动转成NULL,’'和NULL对比判定成立。

2 KES执行规则

KES完全遵循通用SQL标准,空字符串’'和NULL是两种完全不同的数据:
‘’ IS NULL → 结果false
‘’ = ‘’ → 结果true
‘’ = NULL → 结果未知

3 线上高频出错写法

原来Oracle分页语句:

SELECT * FROM tab WHERE name <> '';

Oracle会把这条语句等价成过滤非NULL的数据。迁移到KES之后,只会过滤纯空字符串,表里NULL的数据全部查出来,报表多出一堆无效记录。

4 两边通用兼容写法

WHERE name IS NOT NULL AND name <> ''

迁移阶段批量更新存量数据,统一NULL和空字符串的存储口径。

五、聚合函数多层嵌套、过滤位置区分问题

1 Oracle允许的写法,KES会直接报错

SELECT MAX(SUM(amount)) FROM order_tab GROUP BY dept_id;

Oracle支持聚合嵌套,KES按照标准语法执行,这种写法直接报语法错误。
还有一种容易忽略的坑,把聚合判断写到WHERE里:

SELECT dept_id,SUM(amount) total
FROM order_tab
WHERE SUM(amount) > 1000
GROUP BY dept_id;

Oracle部分版本会自动把条件挪到分组后,KES不会自动处理,直接报错。

整改规范

  1. 多层聚合先用子查询或者CTE算出中间结果,外层再做汇总;
    2 分组之后的筛选条件统一写到HAVING,WHERE只用来过滤原始行。
上一篇 MySQL/PostgreSQL 迁移金仓 KES:LEFT JOIN 丢数据排查与避坑指南