PolarDB PostgreSQL版(兼容Oracle)不仅支持原生PostgreSQL的错误处理系统,还兼容Oracle语法中的常见错误处理机制。
主要包括以下几种类型的错误处理机制:
预定义异常:既支持原生PostgreSQL的所有预定义异常,同时也支持兼容Oracle语法中的常见预定义异常。
非预定义异常:兼容Oracle语法中的异常变量,并支持通过
EXCEPTION_INIT
为异常变量设置错误码。自定义异常(RAISE_APPLICATION_ERROR):兼容Oracle语法中的
RAISE_APPLICATION_ERROR
函数,用于抛出自定义的错误码和错误消息。错误码与错误信息:既支持原生PostgreSQL的
SQLSTATE
错误码,也兼容Oracle语法中常见的SQLCODE
错误码,此外还支持SQLERRM
用于查看详细错误信息。
预定义异常
PolarDB PostgreSQL版(兼容Oracle)既支持原生PostgreSQL的所有预定义异常,如division_by_zero
。同时也支持兼容Oracle语法中的常见预定义异常,如zero_divide
。在SQL中既可以抛出这些异常,也可以捕获这些异常。
抛出异常
异常的抛出分为两种方式:执行PL/SQL语句抛出以及RAISE
语句显式抛出。示例如下:
执行PL/SQL语句抛出异常
DECLARE
result INT;
BEGIN
result := 1 / 0;
END;
RAISE语句显式抛出异常
-- PostgreSQL 风格
BEGIN
RAISE division_by_zero;
END;
-- Oracle 风格
BEGIN
RAISE zero_divide;
END;
捕获异常
异常的捕获通过EXCEPTION
实现,既可以在WHEN
子句中指定异常名以捕获特定异常,也可以通过WHEN OTHERS
捕获所有异常。示例如下:
WHEN子句中指定异常名
-- PostgreSQL 风格
DECLARE
result INT;
BEGIN
result := 1 / 0;
EXCEPTION
WHEN division_by_zero THEN
DBMS_OUTPUT.PUT_LINE('division by zero');
END;
-- Oracle 风格
DECLARE
result INT;
BEGIN
result := 1 / 0;
EXCEPTION
WHEN zero_divide THEN
DBMS_OUTPUT.PUT_LINE('zero divide');
END;
WHEN OTHERS
DECLARE
result INT;
BEGIN
result := 1 / 0;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('division by zero');
END;
非预定义异常
在Oracle语法中,存在大量的错误码,其中只有少数错误码被定义为系统预定义异常。例如,错误码100对应于no_data_found
异常,可以通过显式抛出RAISE no_data_found
来引发该异常,并通过WHEN no_data_found
进行异常捕获。
对于大量的非预定义异常,它们仅具备错误码而没有名称,如何抛出和捕获这些异常呢?为了解决这一问题,Oracle引入了异常变量。PolarDB PostgreSQL版(兼容Oracle)同样支持该特性:
首先在
DECLARE
中声明异常变量,给定一个变量名,例如my_exception EXCEPTION;
。通过
PRAGMA EXCEPTION_INIT
语句为该变量绑定一个SQLCODE
,例如PRAGMA EXCEPTION_INIT(my_exception, -1476);
。重要错误码存在范围限制,需为100(表示
no_data_found
异常)或在[-1000000, 0)
范围内,且只有[-65535, 0)
这个范围能够绑定到Oracle的合法SQLCODE
,其他范围的错误码没有实际意义。在
EXCEPTION
段中可以用WHEN my_exception
或WHEN OTHERS
捕获该异常。
示例
DECLARE
result INT;
my_exception EXCEPTION;
PRAGMA EXCEPTION_INIT(my_exception, -66666);
BEGIN
result := 1 / 0;
EXCEPTION
WHEN my_exception THEN
DBMS_OUTPUT.PUT_LINE('zero divide');
END;
自定义异常(RAISE_APPLICATION_ERROR)
预定义异常和非预定义异常的错误码都属于Oracle预留的错误码范围,通常为[-65535, 0)
范围的负数。除此以外,Oracle还提供了一个自定义异常错误码区间[-20000, -20999]
,通过RAISE_APPLICATION_ERROR
函数即可抛出自定义的错误码和错误消息。
示例
DECLARE
salary NUMBER := 4000;
BEGIN
-- 校验业务规则,不符合条件时触发错误
IF salary < 5000 THEN
RAISE_APPLICATION_ERROR(-20001, 'Salary is below the allowed minimum.');
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('SQLCODE: ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('Custom Error: ' || SQLERRM);
END;
错误码与错误信息
在EXCEPTION
段中通过WHEN
子句捕获到异常以后,如果希望获取异常的各项相关信息,可以通过如下方法:
SQLSTATE:原生PostgreSQL的错误码,由五个字符的字符串组成,前两位表示类别(如语法错误、连接异常等),后三位表示具体错误子类。
SQLCODE:兼容Oracle语法中的错误码,分为三类:
100:表示
no_data_found
异常,查询语句无返回结果。[-65535, 0)
范围的负数:PolarDB PostgreSQL版(兼容Oracle)集群已兼容Oracle语法中的各类错误场景。-99999:表示非法值,PolarDB PostgreSQL版(兼容Oracle)集群暂不支持当前场景。
SQLERRM:错误信息,用于描述异常的详细情况。
示例
DECLARE
result INT;
BEGIN
result := 1 / 0;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('SQLCODE: ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('SQLSTATE: ' || SQLSTATE);
DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
END;
输出结果为:
SQLCODE: -1476
SQLSTATE: 22012
SQLERRM: division by zero
在PolarDB PostgreSQL版(兼容Oracle)集群中,部分Oracle风格的SQLCODE与PostgreSQL风格的SQLSTATE之间存在映射关系,而另一些则不具备直接映射关系。以下为您列举了已支持的错误场景信息:
SQLCODE | Oracle异常名 | SQLSTATE | PostgreSQL异常名 | 错误描述 |
100 | NO_DATA_FOUND | P0002 | no_data_found | 查询语句无返回结果 |
-1 | DUP_VAL_ON_INDEX | 23505 | unique_violation | 插入重复键时违反唯一约束 |
-54 | 无 | 55P03 | lock_not_available | 加锁失败 |
-939 | 无 | 54023 | too_many_arguments | 函数参数过多 |
-957 | 无 | 42701 | duplicate_column | 列名重复 |
-960 | 无 | 42702 | ambiguous_column | 列名不明确 |
-1001 | INVALID_CURSOR | 34000 | invalid_cursor_name | 游标未定义或游标名非法 |
-1031 | 无 | 42501 | insufficient_privilege | 权限不足 |
-1422 | TOO_MANY_ROWS | P0003 | too_many_rows | 查询语句返回行数过多 |
-1428 | 无 | 2201E | invalid_argument_for_logarithm | log函数参数非法 |
-1428 | 无 | 2201F | invalid_argument_for_power_function | power函数参数非法 |
-1476 | ZERO_DIVIDE | 22012 | division_by_zero | 除数为0 |
-1578 | 无 | XX001 | data_corrupted | 数据损坏 |
-1821 | 无 | 22007 | invalid_datetime_format | 日期格式错误 |
-3125 | 无 | 08P01 | protocol_violation | 通信协议错误 |
-4020 | 无 | 40P01 | deadlock_detected | 检测到死锁 |
-6035 | 无 | 53000 | insufficient_resources | 资源不足 |
-6113 | 无 | 53300 | too_many_connections | 连接过多 |
-6503 | 无 | 2F005 | function_executed_no_return_statement | 函数没有RETURN语句 |
-6511 | CURSOR_ALREADY_OPEN | 42P03 | duplicate_cursor | 游标重复定义或重复打开 |
-6531 | COLLECTION_IS_NULL | 2203G | collection_is_null | 集合为空 |
-6532 | SUBSCRIPT_OUTSIDE_LIMIT | 2202E | array_subscript_error | 数组下标越界 |
-6533 | SUBSCRIPT_BEYOND_COUNT | 2203H | subscript_beyond_count | 数组下标越界 |
-6592 | CASE_NOT_FOUND | 20000 | case_not_found | CASE语句没有匹配的WHEN子句 |
-24381 | 无 | P0005 | forall_dml_error | FOR ALL语句报错 |
-27102 | 无 | 53200 | out_of_memory | 内存不足 |
-30110 | 无 | 42601 | syntax_error | 语法错误 |
-30156 | 无 | 53100 | disk_full | 磁盘满 |
-1722 | INVALID_NUMBER | - | - | TO_NUMBER函数参数非法 |
-1723 | 无 | - | - | 列的长度为0 |
-1724 | 无 | - | - | FLOAT类型精度非法 |
-1727 | 无 | - | - | NUMBER类型精度非法 |
-6502 | VALUE_ERROR | - | - | 数字类型格式非法或超过上限 |
-6508 | 无 | - | - | 包函数未定义 |
-99999 | 无 | - | - | 暂不支持当前场景 |