RAISE EXCEPTION会立即终止函数并回滚整个事务(SAVEPOINT除外),日志写入需配合SAVEPOINT或改用RAISE LOG/NOTICE;应使用自定义SQLSTATE(如'45000')配合HINT和DETAIL实现客户端精准识别。

RAISE EXCEPTION 会回滚整个事务,这点必须提前确认
PostgreSQL 中 RAISE EXCEPTION 不是“抛个提示”那么简单——它会立即终止当前函数执行,并触发事务回滚(ROLLBACK TO SAVEPOINT 除外)。如果你在函数里先写了 INSERT INTO log_table,再 RAISE EXCEPTION,那条日志根本不会留存。
常见错误现象:日志表空空如也,但客户端却收到了错误;或者误以为加了 EXCEPTION 块就能“捕获后继续”,结果发现没生效。
- 想留日志?必须用
SAVEPOINT+ROLLBACK TO显式控制,或改用RAISE LOG/RAISE NOTICE配合异步写入(如pg_notify) - 想让调用方区分业务错误和系统错误?优先用自定义
SQLSTATE,比如'45000'(未保留的通用业务错误码),而不是依赖错误消息文本匹配 - 不要在
RAISE EXCEPTION后面写任何语句——它之后的代码永不执行
怎么写一个带 SQLSTATE 和 HINT 的 RAISE EXCEPTION
只写 RAISE EXCEPTION '用户名已存在' 是脆弱的:消息可能被本地化、被截断、被日志系统过滤。真正可控的方式是指定 SQLSTATE 并附加 HINT 或 DETAIL。
示例:
RAISE EXCEPTION 'Duplicate user name'
USING ERRCODE = '45000',
HINT = 'Use UPSERT or check before insert',
DETAIL = 'Input username: ' || username_var;
这样 Java 端就能通过 e.getSQLState() 稳定识别为业务异常,而不依赖 e.getMessage() 的字符串内容。
-
ERECODE必须是 5 位字符串,推荐用'45000'~'45999'区间(PostgreSQL 官方预留的自定义业务错误范围) -
HINT会原样返回给客户端,适合放修复建议;DETAIL更适合放上下文数据(如变量值),默认不显示在 psql 错误里,需设client_min_messages = debug才可见 - 避免在
USING子句中拼接敏感字段(如密码、token),防止泄露到日志或客户端
在 PL/pgSQL 函数里捕获并重新抛出自定义错误
有时你不想直接 RAISE EXCEPTION,而是想先做点清理、记点东西、再抛出更明确的错误——这时要用 BEGIN ... EXCEPTION ... END 块包裹。
注意:EXCEPTION 块只能捕获其内部语句抛出的错误,不能捕获自身 handler 里的新错误。
BEGIN
INSERT INTO orders (id, amount) VALUES (new_id, new_amount);
EXCEPTION
WHEN unique_violation THEN
RAISE EXCEPTION 'Order ID conflict'
USING ERRCODE = '45001',
HINT = 'ID already used, try auto-generate';
WHEN OTHERS THEN
RAISE EXCEPTION 'Unexpected DB error: %', SQLERRM
USING ERRCODE = '45999';
END;
-
WHEN unique_violation是 PostgreSQL 内置异常名,比检查SQLSTATE = '23505'更直观 -
SQLERRM和SQLSTATE只在EXCEPTION块内有效,离开就失效 - 别用
WHEN OTHERS THEN RAISE EXCEPTION ...盲目兜底——它会吞掉关键系统错误(如死锁、权限拒绝),应只用于已知可恢复场景
Java 调用时怎么精准识别这个自定义错误
客户端不能靠 e.getMessage().contains("Duplicate") 判断——网络传输可能截断、JDBC 驱动版本差异会影响格式、多语言环境会翻译消息。
唯一可靠方式是读取 SQLException.getSQLState()。
- 存储过程里用了
USING ERRCODE = '45000',Java 就该写"45000".equals(e.getSQLState()) - PostgreSQL 的
getErrorCode()在多数 JDBC 驱动中返回 0(未映射),不可靠,别依赖 - 如果函数有输出参数(
OUT参数),可在抛异常前赋值,让 Java 通过CallableStatement.getObject("out_msg")拿到补充信息,但要注意:异常发生时,未显式设置的OUT参数仍为NULL
最易被忽略的一点:RAISE EXCEPTION 的错误码一旦发出,就无法被函数内层的 EXCEPTION 捕获——它会穿透所有嵌套块,直达最外层或调用方。设计错误流时,得按“单向退出”来考虑。










