sql server中必须在begin catch块内立即调用error_line()和error_message()获取错误行号与消息,二者仅在此上下文有效且需配合使用;error_line()返回try块内引发错误的物理行号,error_message()返回完整错误文本但不含行号。

SQL Server 中用 ERROR_LINE() 和 ERROR_MESSAGE() 捕获错误上下文
SQL Server 存储过程中无法直接拿到“抛错时的源代码行号”,但可以在 TRY...CATCH 块中用内置函数获取最近一次错误的行号与消息。关键前提是:错误必须发生在 TRY 块内,且调用这些函数的位置必须在 CATCH 块中 —— 在外部或 TRY 里调用会返回 NULL 或不准确值。
常见错误现象是:在 CATCH 里只打印 ERROR_MESSAGE(),却没意识到它不带行号;或者误以为 ERROR_LINE() 能返回调用栈中的上层行号(它只返回引发错误那条语句所在的存储过程内的物理行号)。
-
ERROR_LINE()返回的是触发错误的语句在当前存储过程定义文本中的行号(不是调用位置,也不是批处理行号) -
ERROR_MESSAGE()返回完整错误消息,含系统错误号、严重级等,但不含行号 —— 所以必须和ERROR_LINE()配合使用 - 若存储过程被加密(
WITH ENCRYPTION),ERROR_LINE()仍能返回正确行号,但调试时看不到源码,实际意义打折扣 - 注意:这些函数在
CATCH外、或在嵌套TRY的外层CATCH中调用,可能返回上一个错误的残留值,务必在CATCH开头立即保存
BEGIN TRY
SELECT 1/0; -- 故意出错,假设这行在存储过程中是第 15 行
END TRY
BEGIN CATCH
DECLARE @msg NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE @line INT = ERROR_LINE();
RAISERROR('错误发生在第 %d 行:%s', 16, 1, @line, @msg);
END CATCH
MySQL 存储过程没有原生行号获取能力
MySQL 8.0+ 的存储过程不提供类似 ERROR_LINE() 的函数,GET DIAGNOSTICS 只能拿到错误码(MYSQL_ERRNO)、SQLSTATE 和消息(MESSAGE_TEXT),但不包含位置信息。
这意味着:一旦发生错误,你只知道“错了”,不知道“在哪错的”。尤其在长存储过程中排查很吃力。
- 唯一可行的折中方案是手动埋点:在关键逻辑前加
SET @debug_pos = 'before_update_user';,并在DECLARE ... HANDLER中读取该变量 - 不要依赖注释行号(如
-- line 87),因为 MySQL 不解析注释为行号标记 - 如果开启 general_log 或 error_log,日志里也不会记录存储过程内部行号,只记到调用语句层级
- MySQL 8.0.29+ 引入了
performance_schema.events_statements_history_long,可查最近执行语句,但需提前开启采集,且不能精确定位到出错那行
PostgreSQL 用 GET STACKED DIAGNOSTICS 提取上下文
PostgreSQL 支持在异常处理器中用 GET STACKED DIAGNOSTICS 获取更丰富的错误位置信息,包括文件名、函数名、行号 —— 但前提是函数是用 PL/pgSQL 编写的,且未被内联(SECURITY DEFINER 或 VOLATILE 不影响)。
容易踩的坑是:默认情况下,PL/pgSQL 函数不会记录源文件路径(除非用 CREATE OR REPLACE FUNCTION ... AS $func$ ... $func$ LANGUAGE plpgsql; 这种方式创建并保留源码),所以 PG_CONTEXT 可能为空或只显示内部帧。
- 必须在
EXCEPTION块中立即执行GET STACKED DIAGNOSTICS,延迟赋值会导致信息丢失 -
schema_name、table_name等字段对定位 DML 错误帮助不大,真正有用的是pg_context字段(含调用栈)和column_name(仅约束类错误) - 若函数被
INLINABLE优化掉(如简单 SQL 函数),则堆栈中可能不出现该函数名,行号指向调用者而非函数体内部
BEGIN
INSERT INTO users(name) VALUES (NULL);
EXCEPTION
WHEN NOT_NULL_VIOLATION THEN
GET STACKED DIAGNOSTICS v_context = PG_CONTEXT;
RAISE NOTICE '错误上下文:%:', v_context;
END;
跨数据库统一记录错误位置的现实约束
没有银弹。想让同一个存储过程在 SQL Server / PostgreSQL / MySQL 中都输出准确行号,基本不可行 —— 各平台错误机制设计哲学不同:SQL Server 把行号作为核心诊断字段暴露;PostgreSQL 交由堆栈和上下文承载;MySQL 则干脆没提供。
如果你正在做跨数据库兼容层,最务实的做法是:放弃行号,改用语义化标识。比如在每个逻辑块开头加 SET @step = 'validate_input';,再配合 ERROR_NUMBER() 或 SQLSTATE 做组合判断。这样虽然不够精确,但稳定、可读、不依赖底层实现细节。
真正容易被忽略的是:行号本身在部署后可能失效 —— 比如通过 SSMS 修改存储过程后行号偏移,或 Git 合并引入空行,都会让 ERROR_LINE() 指向错误位置。比起行号,语句级别的唯一标签(如命名事务点、自定义错误码)反而更可靠。










