引言
在"双碳"战略与信创工程的双重驱动下,能源、金融、交通等关键行业的数据库国产化替代已进入深水区。作为国产数据库的代表之一,KingbaseES通过内核级兼容Oracle特性,实现了从语法到生态的全方位适配。本文基于1200+表、60+存储过程的真实迁移案例,解析KingbaseES在Oracle平替过程中的全链路实践,揭示从兼容性挑战到成本优化的系统性解决方案。
KingbaseES与Oracle兼容性:
KingbaseES以内核兼容为基础,打造出涵盖内核、工具和接口的全方位 Oracle 兼容能力。当前Oracle常用能力兼容性已达100%。KingbaseES除兼容基本能力外,还兼容PL/SQL内置包、DBLink等高级能力,从而在客户应用移植时,实现应用无感、平滑迁移。
在基础能力方面,KingbaseES兼容SQL 语法及Oracle过程化语言的语法基础,完成了对数据类型、伪列、常用表达式和条件、系统视图、内置函数、模式对象定义、DML、DQL语句,以及控制语句、存储过程/函数、匿名块、触发器、静态SQL、动态SQL等各方面的兼容。在高级能力方面,KingbaseES支持ROWID,BFILE等特殊类型,支持DBLink同异构数据库访问,物化视图,分区操作,支持完备的内置包功能,提供对客户端通用编程接口(ODBC,JDBC等)和自有接口(OCI,OCCI,Pro*C等)的全面支持,以及兼容Oracle XML/JSON能力,实现对半结构化文档型数据的处理等。此外,KingbaseES还提供了与Oracle对等的空间数据GIS处理能力。

一、数据类型兼容性
1.1 SQL 数据类型
PL/SQL 数据类型包括 SQL 数据类型。
其中有关数据类型和子类型、数据类型比较规则、数据类型转换规则、字符和格式模型的所有信息都适用于 SQL 和 PL/SQL。
BINARY_FLOAT 和 BINARY_DOUBLE 的子类型
PL/SQL 预定义了这些子类型:
SIMPLE_FLOAT,SQL 数据类型 BINARY_FLOAT 的子类型
SIMPLE_DOUBLE,SQL 数据类型 BINARY_DOUBLE 的子类型
每个子类型都具有与其基本类型相同的范围,并且具有 NOT NULL 约束。
如果确定变量不会有 NULL 值,请将其声明为 SIMPLE_FLOAT 或 SIMPLE_DOUBLE,而不是 BINARY_FLOAT 或 BINARY_DOUBLE。 在没有检查空值的情况下,子类型提供了比它们的基类型更好的性能。
1.2 CHAR 和 VARCHAR2 变量
分配或插入过长的值
如果分配给字符变量的值长于变量的最大长度,则会发生错误。例如:
sql
\set SQLTERM /
DECLARE
c VARCHAR2(4 CHAR);
BEGIN
c := 'hello';
END;
/
结果:
sql
ERROR: value too long for type character varying(4)
CONTEXT: PL/SQL function inline_code_block line 4 at assignment
同样,如果将字符变量插入到列中,并且变量的值长于定义的列宽度,则会发生错误。例如:
sql
CREATE TABLE t1 (c CHAR(4 CHAR));
\set SQLTERM /
DECLARE
str VARCHAR2(5 CHAR) := 'hello';
BEGIN
INSERT INTO t1(c) VALUES(str);
END;
/
结果:
sql
ERROR: value too long for column "public"."t1"."c" (actual:5, maximum:4)
CONTEXT: SQL statement "INSERT INTO t1(c) VALUES(str)"
PL/SQL function inline_code_block line 4 at SQL statement
在将字符值分配给变量或将其插入列之前,从字符值中去除空格,请使用 TRIM函数 。例如:
sql
\set SQLTERM /
DECLARE
c VARCHAR2(3 CHAR);
BEGIN
c := TRIM(' qwe ');
INSERT INTO t1(c) VALUES(TRIM(' qwe '));
END;
/
SELECT * FROM t1;
/
结果:
sql
c
------
qwe
(1 row)
1.3 CHAR 和 VARCHAR2 数据类型的区别
CHAR和VARCHAR2数据类型的不同之处在于:
预定义子类型
空白填充的工作原理
值比较
预定义子类型
CHAR 数据类型在 PL/SQL 和 SQL 中都有一个预定义的子类型------CHARACTER。
VARCHAR2 数据类型在 PL/SQL 和 SQL 中都有一个预定义的子类型 VARCHAR,在 PL/SQL 中还有一个预定义的子类型 STRING。
每个子类型具有与其基本类型相同的值范围。
空白填充的工作原理
这解释了在 CHAR 和 VARCHAR2 中使用空白填充的区别和注意事项。
考虑以下情况:
用户分配给变量的值小于变量的最大值。
用户插入到列中的值比定义的列宽度短。
用户从列检索到变量中的值小于变量的最大值。
如果接收者的数据类型是CHAR,PL/SQL 将值填充到最大长度。有关原始值中尾随空格的信息会丢失。
如果接收者的数据类型是VARCHAR2,PL/SQL 既不填充值也不去除尾随空格。字符值被原封不动地分配,不会丢失任何信息。
示例: CHAR 和 VARCHAR2 空白填充差异
在此示例中,CHAR 变量和 VARCHAR2 变量的最大长度为 10 个字符。 每个变量都接收一个由五个字符组成的值,后面有一个空格。
分配给 CHAR 变量的值被空白填充到 10 个字符,并且用户无法判断结果值中的六个尾随空白之一是在在原始值中。
分配给 VARCHAR2 变量的值没有改变,可以看到它有一个尾随空格。
sql
\set SQLTERM /
DECLARE
chr CHAR(10 CHAR);
varchr VARCHAR2(10 CHAR);
BEGIN
chr := 'test ';
varchr := 'test ';
raise notice 'chr = (%)',chr;
raise notice 'varchr = (%)',varchr;
END;
/
结果:
sql
NOTICE: chr = (test )
NOTICE: varchr = (test )
值比较
SQL中比较字符值的规则适用于 PL/SQL 字符变量。
当比较中的一个或两个值的数据类型为 VARCHAR2 或 NVARCHAR2 时,使用非填充比较语义;否则,使用空格填充语义。
1.4 PLS_INTEGER 和 BINARY_INTEGER 数据类型
PL/SQL 数据类型中 PLS_INTEGER 和 BINARY_INTEGER 是相同的。
为简单起见,使用 PLS_INTEGER 来表示 PLS_INTEGER 和 BINARY_INTEGER。
PLS_INTEGER 数据类型存储 -2,147,483,648 到 2,147,483,647 范围内的带符号的32整数。
防止 PLS_INTEGER 溢出
操作两个PLS_INTEGER类型的值,如果值的计算超出PLS_INTEGER 类型的范围,则会引发溢出异常。
对于 PLS_INTEGER 范围之外的计算,请使用 NUMBER。
示例 3-4 计算PLS_INTEGER类型值引发溢出异常
此示例计算两个PLS_INTEGER类型的值,并引发了溢出异常。
sql
\set SQLTERM /
DECLARE
a1 PLS_INTEGER := 2147483647;
a2 PLS_INTEGER := 1;
num NUMBER;
BEGIN
num := a1 + a2;
END;
/
结果:
sql
ERROR: integer out of range
CONTEXT: PL/SQL function inline_code_block line 6 at assignment
二、控制语句兼容性
PL/SQL有三类控制语句:条件选择语句、循环语句和顺序控制语句。
PL/SQL 控制语句的类别有:
条件选择语句,针对不同的数据值运行不同的语句
条件选择语句包括 IF语句 和 CASE语句
循环语句,它使用一系列不同的数据值运行相同的语句
循环语句包括基本的 LOOP语句、FOR LOOP语句 和 WHILE LOOP语句
EXIT 语句将控制转移到循环结束。 CONTINUE 语句退出循环的当前迭代并将控制转移到下一个迭代。 EXIT 和 CONTINUE 都有一个可选的 WHEN 子句,可以通过它指定一个条件
顺序控制语句,这对于 PL/SQL 编程并不重要
顺序控制语句包括转到指定语句的 GOTO 语句和不执行任何操作的 NULL语句
2.1 条件选择语句
IF 和 CASE 语句针对不同的数据值运行不同的语句。
IF 语句根据条件运行或跳过一个或多个语句。 IF 语句有以下形式:
IF THEN
IF THEN ELSE
IF THEN ELSIF
CASE 语句从一系列条件中进行选择,并运行相应的语句。 CASE 语句有以下形式:
简单 CASE 语句,它计算单个表达式并将其与几个值进行比较。
搜索 CASE 语句,它计算多个条件并选择第一个为真的条件。
当要对每个备选方案采取不同的操作时,CASE 语句更适用。
2.2 IF THEN 语句
IF THEN 语句根据条件运行或跳过一个或多个语句。
IF THEN 语句具有以下结构:
sql
IF condition THEN
statements
END IF;
如果condition为真,则statements运行;否则,statements不运行。
示例 : IF THEN 语句
在此示例中,当且仅当 a 大于 b 时,才打印 a - b 的值。
sql
\set SQLTERM /
DECLARE
PROCEDURE p(a int, b int) AS
BEGIN
IF a > b THEN
RAISE NOTICE 'a - b = %', a - b;
END IF;
END;
BEGIN
p(1,2);
p(3,2);
END;
/
结果:
sql
NOTICE: a - b = 1
2.3 简单 CASE 语句
简单CASE语句具有以下结构:
sql
CASE selector
WHEN selector_value_1 THEN statements_1
WHEN selector_value_2 THEN statements_2
...
WHEN selector_value_n THEN statements_n
[ ELSE
else_statements ]
END CASE;
selector 是一个表达式(通常是单个变量)。 每个 selector_value 可以是文本或表达式。 (对于完整的语法,请参见" CASE 语句"。)
简单 CASE语句计算selector值并依次与selector_value值进行比较,直到找到一个相等的值。若找到相等的值则执行相应THEN子句的statements;若没有 selector_value 等于 selector,则 CASE 语句判断是否存在 ELSE ,如果存在则运行 else_statements ,否则引发预定义的异常CASE_NOT_FOUND。
示例 : 简单的 CASE 语句
sql
\set SQLTERM /
DECLARE
grade CHAR(1);
BEGIN
grade := 'B';
CASE grade
WHEN 'A' THEN RAISE NOTICE 'Excellent';
WHEN 'B' THEN RAISE NOTICE 'Very Good';
WHEN 'C' THEN RAISE NOTICE 'Good';
WHEN 'D' THEN RAISE NOTICE 'Fair';
WHEN 'F' THEN RAISE NOTICE 'Poor';
ELSE RAISE NOTICE 'No such grade';
END CASE;
END;
/
结果:
sql
NOTICE: Very Good
2.4 循环语句
循环语句使用一系列不同的值迭代地运行相同的语句。
一个 LOOP 语句包含三个部分:
迭代变量
迭代器
循环执行体
sql
loop_statement ::= [ iteration_scheme ]
LOOP loop_body
END LOOP [ label ];
iteration_scheme ::= WHILE expression | FOR iterator
循环语句有:
基本的LOOP
FOR LOOP
游标FOR LOOP
WHILE LOOP
退出循环的语句有:
EXIT
EXIT WHEN
退出当前循环迭代的语句有:
CONTINUE
CONTINUE WHEN
EXIT、EXIT WHEN、CONTINUE 和 CONTINUE WHEN 可以出现在循环内的任何位置,但不能出现在循环外。建议使用这些语句而不是 GOTO 语句,因为GOTO语句可以通过将控制转移到循环外的语句来退出循环或当前循环的迭代。
引发异常也会退出循环。
LOOP 语句可以被标记,也可以嵌套。 对于嵌套的循环,推荐使用标记以提高可读性。 用户必须确保 END LOOP 语句中的标签与同一循环语句开头的标签匹配,否则程序会报错。
2.5 游标迭代控制
游标迭代控件生成由显式或隐式游标返回的 RECORD 序列。
游标定义是控制表达式。不能将 REVERSE 与游标迭代控件一起使用。
sql
cursor_iteration_control ::=
{
cursor_object|
sql_statement|
dynamic_sql
}
cursor_object 是一个显式的 PL/SQL 游标对象。 sql_statement 是由用户直接在迭代控件中指定了 SQL 语句而创建的隐式 PL/SQL 游标对象。dynamic_sql类似于sql_statement,只不过dynamic_sql为动态SQL语句。
在 PL/SQL 中使用游标迭代控制:
计算游标以创建迭代向量。在向量中获取一个值来迭代创建下一个值。如果迭代向量为空,则无法进行迭代向量创建。 计算循环体,同时对获取的每个迭代值重复相同的操作,直到无法进行迭代控制。
示例: 游标迭代控件
创建 id 到表 T1 中数据的映射。
sql
\set SQLTERM ;
create table t1(id int);
insert into t1 values(1),(2);
\set SQLTERM /
declare
cursor c is SELECT * FROM T1;
begin
FOR r IN c LOOP
RAISE NOTICE 'r = %',r;
END LOOP;
end;
/
结果:
sql
NOTICE: r = (1)
NOTICE: r = (2)
ANONYMOUS BLOCK
2.6 顺序控制语句
与 IF 和 LOOP 语句不同,顺序控制语句 GOTO 和 NULL 对于 PL/SQL 编程并不重要。
很少需要转到指定语句的 GOTO 语句。
NULL 语句什么都不做,但它可以通过明确条件语句的含义和动作来提高可读性。
2.7 GOTO 语句
GOTO 语句表示无条件地将控制转移到标签。标签在其范围内必须是唯一的,并且必须位于可执行语句或 PL/SQL 块之前。运行时,GOTO 语句将控制转移到带标签的语句或块。
对于 GOTO 语句限制,请参见" GOTO 语句"。
谨慎使用 GOTO 语句------过度使用它们会导致代码难以理解和维护。不要使用 GOTO 语句将控制从深度嵌套的结构转移到异常处理程序,而是通过引发异常的方式进入异常处理程序。
三、游标变量参数的子程序参数别名
游标变量参数是指针。因此,如果一个子程序将一个游标变量参数分配给另一个,它们指向的是相同的内存位置。
例如:
该过程有两个游标变量参数,Stu_cv1和Stu_cv2。该过程打开Stu_cv1并将其值(它是一个指针)分配给Stu_cv2,则Stu_cv1、Stu_cv2引用相同的内存位置。当程序关闭Stu_cv1时,它也会关闭Stu_cv2。因此,当过程试图从 Stu_cv2 fetch 时,PL/SQL 会引发异常。
sql
DROP TABLE IF EXISTS stu_temp;
CREATE TABLE stu_temp AS
SELECT * FROM student;
insert into stu_temp values (1001, 'xs', 61);
insert into stu_temp values (1002, 'xd', 74);
insert into stu_temp values (1003, 'xc', 83);
insert into stu_temp values (1004, 'xg', 79);
insert into stu_temp values (1005, 'xl', 98);
\set SQLTERM /
DECLARE
TYPE StuCurTyp IS REF CURSOR;
c1 StuCurTyp;
c2 StuCurTyp;
PROCEDURE get_Stu_data (
Stu_cv1 IN OUT StuCurTyp,
Stu_cv2 IN OUT StuCurTyp
)
IS
Stu_rec stu_temp%ROWTYPE;
BEGIN
OPEN Stu_cv1 FOR SELECT * FROM stu_temp;
Stu_cv2 := Stu_cv1; -- now both variables refer to same location
FETCH Stu_cv1 INTO Stu_rec; -- fetches first row of student
FETCH Stu_cv1 INTO Stu_rec; -- fetches second row of student
FETCH Stu_cv2 INTO Stu_rec; -- fetches third row of student
CLOSE Stu_cv1; -- closes both variables
FETCH Stu_cv2 INTO Stu_rec; -- causes error when get_Stu_data is invoked
END;
BEGIN
get_Stu_data(c1, c2);
END;
/
结果:
sql
ERROR: cursor "<unnamed portal 1>" does not exist
CONTEXT: PL/SQL function get_stu_data line 18 at FETCH
SQL statement "CALL get_Stu_data(c1, c2)"
PL/SQL function inline_code_block line 21 at CALL
四、兼容性工具链与配置
4.1 迁移工具链
KingbaseES提供KDTS、KFS、K-ETL等完整工具链,支持全量迁移、增量同步、异构转换。在某银行账户分级系统迁移中,10TB数据通过K-ETL并行读取、断点续传机制,12小时内完成全量迁移。配合KFS的实时日志解析能力,实现业务零停机切换。
关键参数:
ora_input_emptystr_isnull = on:处理空字符串与NULL的映射
ora_date_style = on:统一日期格式处理
nls_length_semantics = 'BYTE':调整字符语义
search_path = '"$user", public, SCOTT':配置模式搜索路径
4.2 高级兼容
KingbaseES支持DBLink同异构数据库访问,通过pg_cron扩展实现Oracle DBMS_SCHEDULER的等价功能。在某电网调度系统中,通过以下配置实现周期性任务调度:
创建cron任务
sql
SELECT cron.schedule('0 0 * * *', 'SELECT update_salary()');
配置Data Guard热备
sql
SELECT * FROM pg_stat_replication;
对于XML/JSON处理,KingbaseES兼容Oracle的半结构化文档处理能力,支持JSONB类型实现高效查询
五、结语
KingbaseES通过内核级兼容Oracle特性,实现了从语法到生态的全方位适配。当前Oracle常用能力兼容性已达99%,支持PL/SQL内置包、DBLink等高级能力。在基础能力方面,完成了对数据类型、伪列、常用表达式、系统视图、内置函数、DML/DQL语句、控制语句、存储过程/函数、匿名块、触发器、静态SQL、动态SQL等各方面的兼容。
通过系统性解决方案和工具链优化,KingbaseES在Oracle平替过程中实现了从兼容性挑战到成本优化的全链路突破,为关键行业的数据库自主可控提供了坚实支撑。这种从语法到生态的深度兼容,不仅降低了迁移成本,更通过性能优化和架构创新,为企业数字化转型注入了新的动能。