错误处理

PolarDB PostgreSQL版(兼容Oracle)不仅支持原生PostgreSQL的错误处理系统,还兼容Oracle语法中的常见错误处理机制。

主要包括以下几种类型的错误处理机制:

  • 预定义异常:既支持原生PostgreSQL的所有预定义异常,同时也支持兼容Oracle语法中的常见预定义异常。

  • 非预定义异常:兼容Oracle语法中的异常变量,并支持通过EXCEPTION_INIT为异常变量设置错误码。

  • 自定义异常(RAISE_APPLICATION_ERROR):兼容Oracle语法中的RAISE_APPLICATION_ERROR函数,用于抛出自定义的错误码和错误消息。

  • 错误码与错误信息:既支持原生PostgreSQLSQLSTATE 错误码,也兼容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)同样支持该特性:

  1. 首先在DECLARE中声明异常变量,给定一个变量名,例如my_exception EXCEPTION;

  2. 通过PRAGMA EXCEPTION_INIT 语句为该变量绑定一个 SQLCODE,例如PRAGMA EXCEPTION_INIT(my_exception, -1476);

    重要

    错误码存在范围限制,需为100(表示no_data_found异常)或在[-1000000, 0)范围内,且只有[-65535, 0)这个范围能够绑定到Oracle的合法SQLCODE,其他范围的错误码没有实际意义。

  3. 在 EXCEPTION 段中可以用WHEN my_exceptionWHEN 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风格的SQLCODEPostgreSQL风格的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

-

-

暂不支持当前场景