sql server中try...catch仅捕获严重性11–19的运行时错误;编译期错误(如表不存在、语法错)、≤10的警告及≥20的致命错误均无法进入catch块,因其在解析或编译阶段即终止批处理,根本未执行到try内。

SQL Server 存储过程中,只有严重性 11–19 的运行时错误才能被 TRY...CATCH 捕获;编译期错误(如表不存在、语法错)、严重性 ≤10 的警告、以及 ≥20 的致命错误,统统进不了 CATCH 块。
为什么有些错误根本进不了 CATCH 块
不是 TRY...CATCH 不够用,而是很多报错压根没走到执行阶段——SQL Server 在解析或编译语句时就直接终止批处理了,TRY 连门都没机会进。
-
SELECT * FROM NonExistentTable:对象不存在是编译错误,创建存储过程时就报错,不会执行到TRY内 -
DECLARE @x INT = 'abc':类型转换在赋值时静态检查,直接退出,TRY还没开始 -
RAISERROR('msg', 10, 1):严重性=10,属于信息性消息,CATCH忽略;必须写成RAISERROR('msg', 11, 1)或改用THROW - 子查询内除零或类型转换失败(如
SELECT 1/0在WHERE子句中):可能被优化器提前判定,不触发CATCH;需拆成独立语句(如SET @val = (SELECT 1/0))才可捕获
如何在 CATCH 中拿到真实有用的错误信息
@@ERROR 在 CATCH 里已经失效——它只反映上一条语句的错误,而 CATCH 开头的 DECLARE 就会把它覆盖掉。必须用 ERROR_* 函数,且必须在 CATCH 块开头、任何可能中断执行的语句之前调用。
-
ERROR_NUMBER():返回具体错误号,比如547(外键冲突)、2627(唯一约束违例) -
ERROR_SEVERITY()和ERROR_STATE():配合使用才能区分同一错误的不同上下文(例如同个主键冲突在不同索引下状态值不同) -
ERROR_LINE():返回TRY块内出错语句的**相对行号**,不是整个存储过程的绝对行号 -
ERROR_PROCEDURE():仅在存储过程中有效;若在匿名批处理中执行,返回NULL
事务回滚不能靠猜,必须显式判断状态
TRY...CATCH 不会自动回滚事务。如果你在 TRY 里 BEGIN TRANSACTION,又没在 CATCH 里处理,事务会卡在打开状态,后续语句可能意外提交,或阻塞其他会话。
- 别只看
@@TRANCOUNT:嵌套调用时它可能 >1,但事务实际已不可提交(XACT_STATE() = -1) - 优先用
XACT_STATE():返回-1表示事务已损坏,必须ROLLBACK;返回1表示可提交,按业务决定是否COMMIT或ROLLBACK -
THROW要放在CATCH末尾:SQL Server 2012+ 推荐用THROW重抛原始错误,保留错误号、行号和消息;RAISERROR会丢失部分上下文 - 避免在
CATCH里再执行高风险操作(如写日志表):万一日志表也挂了,整个错误处理链就断了
跨数据库异常处理没法“一套代码走天下”
PostgreSQL 用 BEGIN ... EXCEPTION ... END,MySQL 用 DECLARE HANDLER,它们和 SQL Server 的 TRY...CATCH 在语义、触发时机、错误分类粒度上都不兼容。
- PostgreSQL 的
EXCEPTION只能捕获SQLSTATE码(如'23505'),不能通配;WHEN OTHERS THEN后必须用GET STACKED DIAGNOSTICS手动取行号,否则丢关键定位信息 - MySQL 的
DECLARE CONTINUE HANDLER默认不中断执行,容易导致“错误已发生,逻辑还在跑”,必须配LEAVE显式退出 - 想统一记录日志?别硬套通用表结构——各库的
ERROR_MESSAGE()、SQLERRM、MESSAGE_TEXT字段长度、格式、编码都不同,字段对不齐反而拖慢排查
真正麻烦的从来不是怎么写 CATCH,而是哪些错误它根本拦不住——你得提前知道哪些地方必须用 IF OBJECT_ID() 预检、哪些子查询得拎出来单独包一层 TRY、哪些错误只能靠调用方检查 @@ERROR 或返回值来兜底。










