KingbaseES 处理 PL/SQL 运行时错误:从异常捕获到处理
KingbaseES 提供与 Oracle PL/SQL 高度兼容的异常处理机制。文章介绍了三级异常处理架构、系统预定义异常(如 DIVISION_BY_ZERO)和用户自定义异常的声明与触发方式。通过 RAISE 语句可显式触发异常,并展示了在匿名块和存储过程中处理异常的最佳实践,包括重新声明预定义异常的风险及解决方案,帮助开发者构建高可靠的国产数据库应用。

KingbaseES 提供与 Oracle PL/SQL 高度兼容的异常处理机制。文章介绍了三级异常处理架构、系统预定义异常(如 DIVISION_BY_ZERO)和用户自定义异常的声明与触发方式。通过 RAISE 语句可显式触发异常,并展示了在匿名块和存储过程中处理异常的最佳实践,包括重新声明预定义异常的风险及解决方案,帮助开发者构建高可靠的国产数据库应用。

KingbaseES 凭借其与 Oracle PL/SQL 高兼容度,成为关键行业数据库迁移的首选。然而在实际迁移过程中,开发者常遇到相同的 PL/SQL 代码在 Oracle 运行正常,在 KingbaseES 却报错的困惑。

本文梳理 KingbaseES 异常处理机制,从语法差异到运行时错误的完整解决方案。
异常(PL/SQL 运行时错误)可能来自设计错误、编码错误、硬件故障或其他来源。无法预先处理所有可能触发的异常,但可以编写异常处理程序,让程序在触发异常的情况下继续运行。
KingbaseES 三级异常处理架构
当执行到错误语句时,控制流会跳转到对应的 EXCEPTION 块。系统在异常条件列表中匹配当前触发的异常,若匹配成功则执行对应的 HandlerStatements,否则异常会向外层传播直至被捕获或终止程序。
异常处理程序运行后,控制权转移到封闭块的下一条语句。如果没有封闭块会出现下面的情况
如果异常处理程序在子程序中,则将控制权返回给调用者调用之后的语句处 如果异常处理程序位于匿名块中,则控制权转移到主机环境
如果在没有异常处理程序的 PL/SQL 块中触发异常,则异常会传播。也就是说,异常会在连续的封闭块中向上抛出,直到一个 PL/SQL 块有一个异常处理程序或没有封闭块为止,如果没有异常处理程序,那么 PL/SQL 会向调用者或主机环境返回一个未处理的异常,这将决定最终的返回结果。
KingbaseES 异常的种类分为:
系统预定义异常
系统预定义异常是 PL/SQL 已命名的异常,这些异常都有一个错误代码,且会在系统运行出错时隐式(自动)触发。
用户自定义异常
可以在任何 PL/SQL 匿名块、子程序或包的声明部分中声明自己的异常。例如,可以声明一个名为 invalid_number 的异常标记一个无效数字。用户自定义异常必须显式的触发。
异常差异

下面将详细讲解两种异常。
使用异常处理程序进行异常处理使程序更易于编写和理解,并降低了未处理异常的可能性。
如果没有异常处理程序,则必须检查所有可能触发的异常,并处理它。但这样很容易忽略可能出现的异常,尤其是在无法立即检测到异常的情况下(例如,在计算运行之前使用了错误数据可能无法检测到)。异常处理代码可以分散在整个程序中。
使用异常处理程序,不需要预先知道每个可能触发的异常或它可能发生的位置。只需在可能发生错误的每个块中包含一个异常处理模块。在异常处理模块中,用户可以编写处理特定错误或未知错误的异常处理程序。如果块中的任何地方(包括子块内)发生错误,则异常处理程序都会捕获并处理它。错误处理代码则会被隔离在块的异常处理部分。
一个存储过程使用单个异常处理程序来处理预定义异常 NO_DATA_FOUND,该异常可以出现在两个 SELECT INTO 语句中的任何一个中。
如果多个语句使用相同的异常处理程序,并且用户想知道哪个语句触发了异常,则可以使用变量辅助定位。
如果用户可以确定需要处理哪个异常,就可以为特定异常设置一个异常处理程序。用户还可以通过将语句放入具有自己的异常处理程序的块中来检查单个语句中的异常。
针对许多比较常见的异常以及系统运行时会触发的异常,PL/SQL 内部为其预定义了一个名称。例如:除零错误,对应的预定义异常名称为 DIVISION_BY_ZERO。当错误发生时,系统隐式(自动)抛出该异常。
PL/SQL 预定义异常

预定义异常信息可通过数据库系统函数 SYS_GET_PREDEFINED_EXCEPTION_DETAIL 进行查询。
set serverout on;
DECLARE
stock_price NUMBER := 9.73;
net_earnings NUMBER := 0;
pe_ratio NUMBER;
BEGIN
pe_ratio := stock_price / net_earnings; -- raises ZERO_DIVIDE exception
DBMS_OUTPUT.PUT_LINE('Price/earnings ratio = ' || pe_ratio);
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('Company had zero earnings.');
pe_ratio := NULL;
END;
结果:
Company had zero earnings.
DECLARE
stock_price NUMBER := 9.73;
net_earnings NUMBER := 0;
pe_ratio NUMBER;
BEGIN
pe_ratio := CASE net_earnings
WHEN 0 THEN NULL
ELSE stock_price / net_earnings
END;
END;
用户可以在任何 PL/SQL 块,函数、存储过程或者包中声明一个异常。
语法格式:
exception_name EXCEPTION;
用户自定义异常必须被显式触发,有关异常触发的详细信息。
用户自定义异常可以与一个错误码进行绑定,具体语法为:
PRAGMA EXCEPTION_INIT (exception, error_code);
其中 exception 是用户自定义的异常,error_code 是大于 -1000000 且小于 0 的整数,error_code 可以是系统预定义异常的错误码。
建议不要重新声明系统预定义的异常,即声明用户定义的异常名称与系统预定义的异常名称同名。
如果重新声明了系统预定义的异常,那么本地声明将覆盖 STANDARD 包的全局声明。此时只能使用 STANDARD 包名来限定全局异常名称,否则异常处理程序无法处理全局异常。
重新声明预定义异常:
DROP TABLE IF EXISTS t CASCADE;
CREATE TABLE t (c NUMBER(2,1));
在下面的块中,INSERT 语句隐式引发了 VALUE_ERROR 异常,异常处理程序捕获并处理了这个异常。
set serverout on;
DECLARE
default_number NUMBER := 0;
BEGIN
INSERT INTO t VALUES(TO_NUMBER('100.001','9G999'));
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.');
INSERT INTO t VALUES(default_number);
END;
结果:
Substituting default value for invalid number.
以下块重新声明了预定义的异常 VALUE_ERROR。当 INSERT 语句隐式触发预定义异常 VALUE_ERROR 时,异常处理程序不会处理它。
set serverout on;
DECLARE
default_number NUMBER := 0;
value_error EXCEPTION; -- redeclare predefined exception
BEGIN
INSERT INTO t VALUES(TO_NUMBER('100.001','9G999'));
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.');
INSERT INTO t VALUES(default_number);
END;
结果:
ERROR: numeric field overflow
DETAIL: A field with precision 2, scale 1 must round to an absolute value less than 10^1.
CONTEXT: SQL statement "INSERT INTO t VALUES(TO_NUMBER('100.001', '9G999'))"
PL/SQL function inline_code_block line 5 at SQL statement
如果在异常处理程序中使用包名 STANDARD 限定异常名称,则上述块中的异常处理程序将处理预定义的异常 VALUE_ERROR:
set serverout on;
DECLARE
default_number NUMBER := 0;
value_error EXCEPTION; -- redeclare predefined exception
BEGIN
INSERT INTO t VALUES(TO_NUMBER('100.001','9G999'));
EXCEPTION
WHEN STANDARD.VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.');
INSERT INTO t VALUES(default_number);
END;
结果:
Substituting default value for invalid number.
要显式触发异常,使用 RAISE 语句或存储过程 RAISE_APPLICATION_ERROR。
RAISE 语句可以显式的触发一个异常。在异常处理程序之外,RAISE 语句必须指定异常名称。如果在异常处理程序内部,且省略了异常名称,那么该 RAISE 语句将重新引发当前正在处理的异常。
语法格式:
RAISE [ exception ];
其中 exception 可以是已定义的用户自定义异常,也可以是系统预定义异常。
省略 exception 的 RAISE 子句仅可在异常处理模块中使用。
存储过程声明了一个名为 past_due 的异常,使用 RAISE 语句显式触发它,并使用异常处理程序处理它。
声明、触发和处理用户定义的异常
CREATE PROCEDURE account_status (due_date DATE, today DATE) AUTHID DEFINER IS
past_due EXCEPTION; -- declare exception
BEGIN
IF due_date < today THEN
RAISE past_due; -- explicitly raise exception
END IF;
EXCEPTION
WHEN past_due THEN -- handle exception
RAISE NOTICE 'Account past due.';
END;
调用存储过程:
BEGIN
account_status(TO_DATE('01-JUL-2010','DD-MON-YYYY'), TO_DATE('09-JUL-2010','DD-MON-YYYY'));
END;
输出结果:
NOTICE: Account past due.
系统预定义异常通常由系统运行时隐式触发,但也可以使用 RAISE 语句显式地触发它们。当一个预定义异常拥有对应的异常处理程序时,无论是显式触发还是隐式触发,都会触发异常处理程序对相应的异常进行处理。
存储过程不论显式或是隐式触发预定义异常 VALUE_ERROR,异常处理程序始终都会处理它。
触发系统预定义异常
DROP TABLE IF EXISTS t CASCADE;
CREATE TABLE t (c NUMBER(2,1));
CREATE PROCEDURE p (n NUMBER) AUTHID DEFINER IS
default_number NUMBER := 0;
BEGIN
IF n < 0 THEN
RAISE VALUE_ERROR; -- 显式触发
ELSE
INSERT INTO t VALUES(TO_NUMBER('100.001','9G999')); -- 隐式触发
END IF;
EXCEPTION
WHEN VALUE_ERROR THEN
RAISE NOTICE 'Substituting default value for invalid number.';
INSERT INTO t VALUES(default_number);
END;
调用存储过程:
BEGIN
p(-1);
END;
输出结果:
NOTICE: Substituting default value for invalid number.
再次调用存储过程:
BEGIN
p(1);
END;
输出结果:
NOTICE: Substituting default value for invalid number.
KingbaseES 通过完善的异常处理机制,实现了从语法兼容到运行时行为的全面 Oracle 兼容。其三级异常处理架构、丰富的预定义异常、灵活的自定义异常能力,配合智能诊断工具,构建了完整的异常处理生态体系。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
在线格式化和美化您的 SQL 查询(它支持各种 SQL 方言)。 在线工具,SQL 美化和格式化在线工具,online
解析 INSERT 等受限 SQL,导出为 CSV、JSON、XML、YAML、HTML 表格(见页内语法说明)。 在线工具,SQL转CSV/JSON/XML在线工具,online
CSV 与 JSON/XML/HTML/TSV/SQL 等互转,单页多 Tab。 在线工具,CSV 工具包在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online