错误处理机制
熟悉 Java 语言的朋友们一定对 try - catch - finally 不会陌生。try - catch - finally 是一个经典的错误处理机制,其框架如下:
try {
// 可能发生错误或异常的代码逻辑
} catch(err) {
// 错误处理逻辑
} finally {
// 返回前执行逻辑
}
try 语句块中的代码可能报错或出现异常。如果 try 中代码执行正常,那么就跳过 catch 中的代码逻辑。一旦 try 中代码报错,catch 代码块就会捕捉该错误,并对错误进行处理。对于处理不了的异常,可以在 catch 块中 通过 throw 语句拋出异常,由上层的调用方法来处理该异常;或在可能抛出错误的方法声明处通过 throws 声明异常。无论是否出现错误,finally 语句块中的逻辑都会执行。
PostgreSQL 中的 TRY - CATCH
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 宏在这种情况下可能会有所帮助。