formatmessage是模板填充器而非错误生成器,仅返回格式化字符串;需配合raiserror或throw抛错,三种输入方式不可混用,参数类型数量须严格匹配,本地化占位符顺序必须一致,生产环境应补全元数据并防注入。

FORMATMESSAGE 本身不生成错误,只返回格式化后的字符串;真要抛错,必须配合 RAISERROR 或 THROW。它不是“动态错误生成器”,而是“模板填充器”——用对了才可控,用错了连 NULL 都不报,静默失败。
FORMATMESSAGE 的三种输入方式不能混用
传第一个参数时,msg_number、'msg_string'、@msg_variable 行为完全不同,选错就得不到预期结果:
-
msg_number(如50001):查sys.messages,但仅限已用sp_addmessage注册的自定义错误(且msg_number > 50000),系统错误号 ≤13000 一律返回NULL -
'msg_string'(如'User %s not found'):单引号内联,只支持%s占位符(不认%d),最长 2047 字符,超长静默截断为 2044 +… -
@msg_variable:变量类型必须是nvarchar或varchar,内容得符合上述字符串规则;若声明为varchar(100)却存 200 字符,截断后占位符错位,填充结果不可信
本地化消息的占位符顺序必须严格对齐
同一 msg_number 在不同语言(如 language_id = 1033 英文 / 2052 中文)下注册时,%s、%d 出现的顺序和数量必须完全一致。否则 FORMATMESSAGE(50001, @user, @id) 在中文环境下可能把 @user 塞进数字位置。
验证方法:
SELECT msg_id, language_id, text FROM sys.messages WHERE msg_id = 50001 AND language_id IN (1033, 2052);
比对两行 text 中占位符的顺序——不是看语义通不通,是看 %s 第几个出现、%d 第几个出现。
参数类型和数量必须硬匹配
FORMATMESSAGE 不校验参数个数与占位符是否一致:少给参数,%s 留原样;多给参数,多余值被丢弃;类型错则直接报错 Msg 1001 或返回 NULL:
-
%s只接字符串,传NULL→ 整个结果变NULL(不会转成空串) -
%d/%i只接受整数(INT、BIGINT),传VARCHAR会失败 - 占位符有 2 个,就必须传 2 个参数,差一个都出问题
生产环境别只靠 FORMATMESSAGE 拼通知
单纯拼字符串漏上下文,比如没带时间、SPID、数据库名,排查时抓瞎。更稳妥的做法是把它当模板引擎一环:
- 用
GETDATE()、@@SPID、DB_NAME()补充运行时元数据 - 若模板来自配置表或用户输入,先做
REPLACE(@input, '''', '''''')防 SQL 注入 - 加
TRY...CATCH捕获FORMATMESSAGE自身失败(如参数类型错),避免通知逻辑中断
真正难的不是写那行 FORMATMESSAGE,而是确保每次取到的消息文本和当前语言、版本、sp_addmessage 注册状态一致——跨服务器迁移后,sp_addmessage 没重跑,msg_number 就查不到。











