searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

PostgreSQL 中的错误处理机制

2023-09-26 06:14:22
194
0

错误处理机制

        熟悉 Java 语言的朋友们一定对 try - catch - finally 不会陌生。try - catch - finally 是一个经典的错误处理机制,其框架如下:

try {
  // 可能发生错误或异常的代码逻辑
} catch(err) {
   // 错误处理逻辑
} finally {
  // 返回前执行逻辑
}

        try 语句块中的代码可能报错或出现异常。如果 try 中代码执行正常,那么就跳过 catch 中的代码逻辑。一旦 try 中代码报错,catch 代码块就会捕捉该错误,并对错误进行处理。对于处理不了的异常,可以在 catch 块中 通过 throw 语句拋出异常,由上层的调用方法来处理该异常;或在可能抛出错误的方法声明处通过 throws 声明异常。无论是否出现错误,finally 语句块中的逻辑都会执行。

PostgreSQL 中的 TRY - CATCH

        PostgreSQL 中的错误捕捉机制是使用 PG_TRY()、PG_CATCH() 和 PG_END_TRY()。在 PG_TRY() 后代码块中执行的代码可能会报错,此时会跳转至 PG_CATCH() 后代码块内容。具体使用示例如下(analyze.c 中的 StoreQueryAnalyzeInfo 函数节选):
PG_TRY();
{
    set_portable_output(true);
    plan = nodeToString(plannedstmt);
}
PG_CATCH();
{
    set_portable_output(false);
    PG_RE_THROW();
}
PG_END_TRY();

        与 Java 中的框架类似,PG_TRY()、PG_CATCH()代码块中分别为可能抛出错误的代码逻辑与错误处理逻辑,根据 C 代码风格,PG_TRY() 块最后有相应的 PG_END_TRY() 表明整段代码逻辑的结束。

        PG_TRY()、PG_CATCH()、PG_END_TRY() 都是宏,定义如下(src/include/utils/elog.h):

#define PG_TRY()  \
    do { \
        sigjmp_buf *save_exception_stack = PG_exception_stack; \
        ErrorContextCallback *save_context_stack = error_context_stack; \
        sigjmp_buf local_sigjmp_buf; \
        if (sigsetjmp(local_sigjmp_buf, 0) == 0) \
        { \
            PG_exception_stack = &local_sigjmp_buf

#define PG_CATCH()    \
        } \
        else \
        { \
            PG_exception_stack = save_exception_stack; \
            error_context_stack = save_context_stack

#define PG_END_TRY()  \
        } \
        PG_exception_stack = save_exception_stack; \
        error_context_stack = save_context_stack; \
    } while (0)

         建议使用格式如下:

PG_TRY();
{
    ... code that might throw ereport(ERROR) ...
}
PG_CATCH();
{
    ... error recovery code ...
}
PG_END_TRY();

        因此,采取宏定义替换之后,实际 TRY - CATCH 块代码模块如下:

do {
    sigjmp_buf *save_exception_stack = PG_exception_stack;
    ErrorContextCallback *save_context_stack = error_context_stack;
    sigjmp_buf local_sigjmp_buf;
    if (sigsetjmp(local_sigjmp_buf, 0) == 0)
    {
        PG_exception_stack = &local_sigjmp_buf;
        {
            TRY CODE
                
        }
    }
    else
    {
        PG_exception_stack = save_exception_stack;
        error_context_stack = save_context_stack;
        {
            CATCH CODE
        }
    }
    PG_exception_stack = save_exception_stack;
    error_context_stack = save_context_stack;
} while (0);

        由此可见,PG 中的 TRY - CATCH 实际是一个 if - else 语句,判断条件是 sigsetjmp 函数。sigsetjmp 函数就是 C 的标准库函数 setjmp 。

int setjmp(jmp_buf environment)

        C 库宏 int setjmp(jmp_buf environment) :创建本地的 jmp_buf 缓冲区并且初始化,用于将来跳转回此处。这个子程序保存程序的调用环境于 env 参数所指的缓冲区,env 将被 longjmp 使用。如果是从 setjmp 直接调用返回,setjmp 返回值为0。如果是从 longjmp 恢复的程序调用环境返回,setjmp 返回非零值。

         如果 try 代码块中逻辑出错,程序将跳转至 if 语句重新执行,由于返回值非零,因此会走 else 分支中的 catch 块代码逻辑。

        PG_CATCH() 代码块中的错误恢复代码可以选择调用 PG_RE_THROW() 将错误传至外部。值得注意的是,在 PG_CATCH() 代码块中也可能出现新的错误,虽然该模块能够将这些新的 ereport(ERROR) 正确地进行传播,但是能保证正确传递的错误堆栈级数(number of levels)是有限制的,因此应当尽量确保 PG_CATCH() 块中的代码足够简单,不会产生任何新错误,至少不要在错误堆栈 pop 之前。

        需要注意的是,ereport(FATAL) 将不会被该构造捕获,控制结构将直接通过 proc_exit() 退出。因此,不要将任何非进程本地资源的清理放在错误恢复部分,至少要考虑在ereport(FATAL) 期间会发生什么。storage/ipc.h提供的PG_ENSURE_ERROR_CLEANUP 宏在这种情况下可能会有所帮助。

0条评论
0 / 1000
LiangYi
3文章数
0粉丝数
LiangYi
3 文章 | 0 粉丝
LiangYi
3文章数
0粉丝数
LiangYi
3 文章 | 0 粉丝
原创

PostgreSQL 中的错误处理机制

2023-09-26 06:14:22
194
0

错误处理机制

        熟悉 Java 语言的朋友们一定对 try - catch - finally 不会陌生。try - catch - finally 是一个经典的错误处理机制,其框架如下:

try {
  // 可能发生错误或异常的代码逻辑
} catch(err) {
   // 错误处理逻辑
} finally {
  // 返回前执行逻辑
}

        try 语句块中的代码可能报错或出现异常。如果 try 中代码执行正常,那么就跳过 catch 中的代码逻辑。一旦 try 中代码报错,catch 代码块就会捕捉该错误,并对错误进行处理。对于处理不了的异常,可以在 catch 块中 通过 throw 语句拋出异常,由上层的调用方法来处理该异常;或在可能抛出错误的方法声明处通过 throws 声明异常。无论是否出现错误,finally 语句块中的逻辑都会执行。

PostgreSQL 中的 TRY - CATCH

        PostgreSQL 中的错误捕捉机制是使用 PG_TRY()、PG_CATCH() 和 PG_END_TRY()。在 PG_TRY() 后代码块中执行的代码可能会报错,此时会跳转至 PG_CATCH() 后代码块内容。具体使用示例如下(analyze.c 中的 StoreQueryAnalyzeInfo 函数节选):
PG_TRY();
{
    set_portable_output(true);
    plan = nodeToString(plannedstmt);
}
PG_CATCH();
{
    set_portable_output(false);
    PG_RE_THROW();
}
PG_END_TRY();

        与 Java 中的框架类似,PG_TRY()、PG_CATCH()代码块中分别为可能抛出错误的代码逻辑与错误处理逻辑,根据 C 代码风格,PG_TRY() 块最后有相应的 PG_END_TRY() 表明整段代码逻辑的结束。

        PG_TRY()、PG_CATCH()、PG_END_TRY() 都是宏,定义如下(src/include/utils/elog.h):

#define PG_TRY()  \
    do { \
        sigjmp_buf *save_exception_stack = PG_exception_stack; \
        ErrorContextCallback *save_context_stack = error_context_stack; \
        sigjmp_buf local_sigjmp_buf; \
        if (sigsetjmp(local_sigjmp_buf, 0) == 0) \
        { \
            PG_exception_stack = &local_sigjmp_buf

#define PG_CATCH()    \
        } \
        else \
        { \
            PG_exception_stack = save_exception_stack; \
            error_context_stack = save_context_stack

#define PG_END_TRY()  \
        } \
        PG_exception_stack = save_exception_stack; \
        error_context_stack = save_context_stack; \
    } while (0)

         建议使用格式如下:

PG_TRY();
{
    ... code that might throw ereport(ERROR) ...
}
PG_CATCH();
{
    ... error recovery code ...
}
PG_END_TRY();

        因此,采取宏定义替换之后,实际 TRY - CATCH 块代码模块如下:

do {
    sigjmp_buf *save_exception_stack = PG_exception_stack;
    ErrorContextCallback *save_context_stack = error_context_stack;
    sigjmp_buf local_sigjmp_buf;
    if (sigsetjmp(local_sigjmp_buf, 0) == 0)
    {
        PG_exception_stack = &local_sigjmp_buf;
        {
            TRY CODE
                
        }
    }
    else
    {
        PG_exception_stack = save_exception_stack;
        error_context_stack = save_context_stack;
        {
            CATCH CODE
        }
    }
    PG_exception_stack = save_exception_stack;
    error_context_stack = save_context_stack;
} while (0);

        由此可见,PG 中的 TRY - CATCH 实际是一个 if - else 语句,判断条件是 sigsetjmp 函数。sigsetjmp 函数就是 C 的标准库函数 setjmp 。

int setjmp(jmp_buf environment)

        C 库宏 int setjmp(jmp_buf environment) :创建本地的 jmp_buf 缓冲区并且初始化,用于将来跳转回此处。这个子程序保存程序的调用环境于 env 参数所指的缓冲区,env 将被 longjmp 使用。如果是从 setjmp 直接调用返回,setjmp 返回值为0。如果是从 longjmp 恢复的程序调用环境返回,setjmp 返回非零值。

         如果 try 代码块中逻辑出错,程序将跳转至 if 语句重新执行,由于返回值非零,因此会走 else 分支中的 catch 块代码逻辑。

        PG_CATCH() 代码块中的错误恢复代码可以选择调用 PG_RE_THROW() 将错误传至外部。值得注意的是,在 PG_CATCH() 代码块中也可能出现新的错误,虽然该模块能够将这些新的 ereport(ERROR) 正确地进行传播,但是能保证正确传递的错误堆栈级数(number of levels)是有限制的,因此应当尽量确保 PG_CATCH() 块中的代码足够简单,不会产生任何新错误,至少不要在错误堆栈 pop 之前。

        需要注意的是,ereport(FATAL) 将不会被该构造捕获,控制结构将直接通过 proc_exit() 退出。因此,不要将任何非进程本地资源的清理放在错误恢复部分,至少要考虑在ereport(FATAL) 期间会发生什么。storage/ipc.h提供的PG_ENSURE_ERROR_CLEANUP 宏在这种情况下可能会有所帮助。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0