SQL 存储过程异常处理最佳实践

1次阅读

sql server 的 trycatch 无法捕获语法错误,因其仅处理运行时错误(如除零、约束冲突),而语法或对象不存在等编译期错误在存储过程创建时即报错,根本不会执行到 try 块中。

SQL 存储过程异常处理最佳实践

SQL Server 中 TRY...CATCH 为什么捕不到语法错误?

因为 TRY...CATCH 只捕获运行时错误(如除零、主键冲突),不捕获编译期错误(如表不存在、拼错关键字)。这类错误在存储过程创建时就报错,根本不会执行到 TRY 块里。

常见现象:CREATE PROCEDURE 执行失败,提示 Invalid Object name 'xxx',但你在 TRY 里包了 select * FROM xxx —— 没用,这步压根没机会运行。

  • 真正能被 TRY...CATCH 捕获的: INSERT 违反约束、CONVERT 类型转换失败、RAISError 主动抛出的错误
  • 检查语法/对象是否存在,得靠 if OBJECT_ID('xxx') IS NOT NULL 或动态 SQL + EXEC sp_executesql 配合 TRY...CATCH
  • SQL Server 2019+ 支持 throw,比 RAISERROR 更准确保留原始错误号和状态,推荐替代

postgresql 存储过程怎么写异常处理?

PostgreSQL 没有 TRY...CATCH,用 BEGIN ... EXCEPTION 块,且只支持在 PL/pgSQL 函数中使用,不能直接用于 DO 块或纯 SQL 存储过程。

关键限制:异常处理必须写在函数体内部,且每个 EXCEPTION 分支只能捕获一种 SQLSTATE 错误码,不能像 SQL Server 那样用通配符。

  • 常用 SQLSTATE:'23505'(唯一约束违规)、'23503'(外键违规)、'42703'(列不存在)
  • 别写 WHEN OTHERS THEN 后直接 RETURN —— 会吞掉关键上下文,至少记录 GET STACKED DIAGNOSTICS 获取错误位置
  • 注意:在 INSERT ... ON CONFLICT 场景下,优先用原生冲突处理,而不是靠异常捕获,性能差一个数量级

mysql 存储过程中 DECLARE HANDLER 的坑

MySQL 的异常处理依赖 DECLARE HANDLER,但它对错误类型的识别很粗糙 —— 比如 SQLSTATE 'HY000' 是通用码,可能覆盖了本该重试的锁等待(1205 死锁)和该拒绝的权限错误(1045)。

更麻烦的是:Handler 一旦触发,后续语句默认继续执行,不像 SQL Server 那样自动退出 TRY 块。容易造成“错误已发生,但逻辑还在往下跑”的诡异行为。

  • 必须显式加 LEAVEITERATE 控制流程,否则 handler 执行完就接着跑下一行
  • 不要用 DECLARE continue HANDLER 处理严重错误;改用 DECLARE EXIT HANDLER 确保及时退出
  • SQLSTATE 和 MySQL 错误号(如 1062)可以混用,但优先用 SQLSTATE,兼容性更好

数据库统一异常日志怎么落地?

没有银弹。各数据库的错误结构、可读字段、上下文信息差异太大,硬套一个“通用日志表”反而让排查更慢。

实际建议是分层记录:数据库层只记最小必要字段(错误号、SQLSTATE、发生时间、语句前 100 字符),应用层再补全调用、参数、用户 ID 等上下文。

  • SQL Server:从 ERROR_NUMBER()ERROR_MESSAGE()ERROR_LINE() 取值,别依赖 ERROR_PROCEDURE() —— 动态 SQL 里它常为空
  • PostgreSQL:用 SQLERRMPG_CONTEXT,但注意 PG_CONTEXT 在嵌套函数里可能截断
  • MySQL:GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE 是唯一可靠方式,@@error_count 不够细

最易被忽略的一点:所有数据库里,事务中的异常处理都默认不自动回滚——你得自己写 ROLLBACK 或设 SET XACT_ABORT ON(SQL Server)/ ON ERROR ROLLBACK(PostgreSQL 的 plpgsql 函数内)。

text=ZqhQzanResources