引言
在"双碳"战略与信创工程的双重驱动下,能源、金融、交通等关键行业的数据库国产化替代已进入深水区。作为国产数据库的代表之一,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 变量
分配或插入过长的值
如果分配给字符变量的值长于变量的最大长度,则会发生错误。例如:
\setSQLTERM/DECLAREc VARCHAR2(4CHAR);BEGINc :='hello';END;/结果:
ERROR:valuetoo longfortypecharactervarying(4)CONTEXT: PL/SQLfunctioninline_code_block line4at assignment同样,如果将字符变量插入到列中,并且变量的值长于定义的列宽度,则会发生错误。例如:
CREATETABLEt1(cCHAR(4CHAR));\setSQLTERM/DECLAREstr VARCHAR2(5CHAR):='hello';BEGININSERTINTOt1(c)VALUES(str);END;/结果:
ERROR:valuetoo longforcolumn"public"."t1"."c"(actual:5,maximum:4)CONTEXT:SQLstatement"INSERT INTO t1(c) VALUES(str)"PL/SQLfunctioninline_code_block line4atSQLstatement在将字符值分配给变量或将其插入列之前,从字符值中去除空格,请使用 TRIM函数 。例如:
\setSQLTERM/DECLAREc VARCHAR2(3CHAR);BEGINc :=TRIM(' qwe ');INSERTINTOt1(c)VALUES(TRIM(' qwe '));END;/SELECT*FROMt1;/结果:
c------qwe(1row)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 变量的值没有改变,可以看到它有一个尾随空格。
\setSQLTERM/DECLAREchrCHAR(10CHAR);varchr VARCHAR2(10CHAR);BEGINchr :='test ';varchr :='test ';raise notice'chr = (%)',chr;raise notice'varchr = (%)',varchr;END;/结果:
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类型的值,并引发了溢出异常。
\setSQLTERM/DECLAREa1 PLS_INTEGER :=2147483647;a2 PLS_INTEGER :=1;num NUMBER;BEGINnum :=a1+a2;END;/结果:
ERROR:integeroutofrange CONTEXT: PL/SQLfunctioninline_code_block line6at 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 语句具有以下结构:
IFconditionTHENstatementsENDIF;如果condition为真,则statements运行;否则,statements不运行。
示例 : IF THEN 语句
在此示例中,当且仅当 a 大于 b 时,才打印 a - b 的值。
\setSQLTERM/DECLAREPROCEDUREp(aint,bint)ASBEGINIFa>bTHENRAISE NOTICE'a - b = %',a-b;ENDIF;END;BEGINp(1,2);p(3,2);END;/结果:
NOTICE: a-b=12.3 简单 CASE 语句
简单CASE语句具有以下结构:
CASEselectorWHENselector_value_1THENstatements_1WHENselector_value_2THENstatements_2...WHENselector_value_nTHENstatements_n[ELSEelse_statements]ENDCASE;selector 是一个表达式(通常是单个变量)。 每个 selector_value 可以是文本或表达式。 (对于完整的语法,请参见“ CASE 语句”。)
简单 CASE语句计算selector值并依次与selector_value值进行比较,直到找到一个相等的值。若找到相等的值则执行相应THEN子句的statements;若没有 selector_value 等于 selector,则 CASE 语句判断是否存在 ELSE ,如果存在则运行 else_statements ,否则引发预定义的异常CASE_NOT_FOUND。
示例 : 简单的 CASE 语句
\setSQLTERM/DECLAREgradeCHAR(1);BEGINgrade :='B';CASEgradeWHEN'A'THENRAISE NOTICE'Excellent';WHEN'B'THENRAISE NOTICE'Very Good';WHEN'C'THENRAISE NOTICE'Good';WHEN'D'THENRAISE NOTICE'Fair';WHEN'F'THENRAISE NOTICE'Poor';ELSERAISE NOTICE'No such grade';ENDCASE;END;/结果:
NOTICE: Very Good2.4 循环语句
循环语句使用一系列不同的值迭代地运行相同的语句。
一个 LOOP 语句包含三个部分:
迭代变量
迭代器
循环执行体
loop_statement ::=[iteration_scheme]LOOPloop_bodyENDLOOP[label];iteration_scheme ::=WHILEexpression|FORiterator循环语句有:
基本的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 与游标迭代控件一起使用。
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 中数据的映射。
\setSQLTERM;createtablet1(idint);insertintot1values(1),(2);\setSQLTERM/declarecursorcisSELECT*FROMT1;beginFORrINcLOOPRAISE NOTICE'r = %',r;ENDLOOP;end;/结果:
NOTICE: r=(1)NOTICE: r=(2)ANONYMOUS BLOCK2.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 会引发异常。
DROPTABLEIFEXISTSstu_temp;CREATETABLEstu_tempASSELECT*FROMstudent;insertintostu_tempvalues(1001,'xs',61);insertintostu_tempvalues(1002,'xd',74);insertintostu_tempvalues(1003,'xc',83);insertintostu_tempvalues(1004,'xg',79);insertintostu_tempvalues(1005,'xl',98);\setSQLTERM/DECLARETYPEStuCurTypISREFCURSOR;c1 StuCurTyp;c2 StuCurTyp;PROCEDUREget_Stu_data(Stu_cv1INOUTStuCurTyp,Stu_cv2INOUTStuCurTyp)ISStu_rec stu_temp%ROWTYPE;BEGINOPENStu_cv1FORSELECT*FROMstu_temp;Stu_cv2 :=Stu_cv1;-- now both variables refer to same locationFETCHStu_cv1INTOStu_rec;-- fetches first row of studentFETCHStu_cv1INTOStu_rec;-- fetches second row of studentFETCHStu_cv2INTOStu_rec;-- fetches third row of studentCLOSEStu_cv1;-- closes both variablesFETCHStu_cv2INTOStu_rec;-- causes error when get_Stu_data is invokedEND;BEGINget_Stu_data(c1,c2);END;/结果:
ERROR:cursor"<unnamed portal 1>"doesnotexist CONTEXT: PL/SQLfunctionget_stu_data line18atFETCHSQLstatement"CALL get_Stu_data(c1, c2)"PL/SQLfunctioninline_code_block line21atCALL四、兼容性工具链与配置
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任务
SELECTcron.schedule('0 0 * * *','SELECT update_salary()');配置Data Guard热备
SELECT*FROMpg_stat_replication;对于XML/JSON处理,KingbaseES兼容Oracle的半结构化文档处理能力,支持JSONB类型实现高效查询
五、结语
KingbaseES通过内核级兼容Oracle特性,实现了从语法到生态的全方位适配。当前Oracle常用能力兼容性已达99%,支持PL/SQL内置包、DBLink等高级能力。在基础能力方面,完成了对数据类型、伪列、常用表达式、系统视图、内置函数、DML/DQL语句、控制语句、存储过程/函数、匿名块、触发器、静态SQL、动态SQL等各方面的兼容。
通过系统性解决方案和工具链优化,KingbaseES在Oracle平替过程中实现了从兼容性挑战到成本优化的全链路突破,为关键行业的数据库自主可控提供了坚实支撑。这种从语法到生态的深度兼容,不仅降低了迁移成本,更通过性能优化和架构创新,为企业数字化转型注入了新的动能。