formatmessage仅格式化字符串,不自动写日志;须配合insert或raiserror使用,且参数类型受限、不可为null,需显式转换;高并发下建议优先用concat替代以避免性能与上下文错位问题。

FORMATMESSAGE 不能直接用于普通日志表插入
SQL Server 的 FORMATMESSAGE 函数本身不写日志,它只做字符串格式化——类似 C 语言的 sprintf。你不能把它当 PRINT 或 RAISERROR 用,也不能直接塞进 INSERT INTO logs... 里就指望自动记录时间、线程、级别等信息。它返回的是纯文本,后续必须显式调用 INSERT 或 RAISERROR 才能落地。
常见错误是这样写:
FORMATMESSAGE('用户 %s 登录失败,尝试次数:%d', @username, @attempts)
这段代码执行后什么也不会发生,只是计算出一个字符串值,若没接 SELECT、SET 或 INSERT,结果就丢掉了。
必须配合 RAISERROR 或 INSERT 使用才有效
真正生成可追踪的系统提示日志,有两个主流路径:
- 用
RAISERROR(..., 10, 1) WITH LOG:把格式化后的消息写入 SQL Server 错误日志(sys.fn_syslog不可见,需用sp_readerrorlog查),适合告警类提示 - 用
INSERT INTO your_log_table:手动构造日志行,把FORMATMESSAGE结果作为message字段值,同时补上GETDATE()、ORIGINAL_LOGIN()、@level等上下文字段
示例(写入自定义日志表):
INSERT INTO app_logs (log_time, level, message, user_name, session_id)
SELECT GETDATE(), 'INFO',
FORMATMESSAGE('订单 %d 已提交,金额 %.2f 元', @order_id, @amount),
ORIGINAL_LOGIN(), @@SPID;
FORMATMESSAGE 的参数限制很严格
它不接受任意类型参数,只支持 int、smallint、tinyint、char/varchar、nchar/nvarchar,且不能传 NULL(会报错)。遇到 datetime、decimal、xml 或空值,必须先显式转换:
-
FORMATMESSAGE('时间:%s', CONVERT(varchar, @dt, 120))——datetime必须转字符串 -
FORMATMESSAGE('价格:%s', ISNULL(CONVERT(varchar, @price, 2), 'N/A'))——NULL要兜底 - 占位符顺序必须和参数顺序严格一致,不支持命名参数(如
%(name)s)
另外,FORMATMESSAGE 默认查找系统消息号,如果第一个参数是整数(比如 FORMATMESSAGE(50000, ...)),它会去 sys.messages 查对应 ID 的模板,而不是当作字面量字符串。想用自定义文本,第一个参数必须是 nvarchar 类型字面量。
性能与并发场景下的注意事项
FORMATMESSAGE 是标量函数,在大结果集循环中调用(比如 SELECT ... FROM orders CROSS APPLY (...) AS msg)会产生明显开销,尤其是带多层 CONVERT 和 ISNULL 时。更严重的是,它在并行计划中可能被推到非预期执行分支,导致日志内容和实际数据行错位。
建议做法:
- 日志频次高、字段简单时,用字符串拼接(
CONCAT或+)替代FORMATMESSAGE,省去类型校验开销 - 必须用
FORMATMESSAGE做国际化或集中模板管理时,确保日志写入走单独事务或异步队列,避免阻塞主业务逻辑 - 不要在触发器里大量调用它——触发器本身已处于事务关键路径,再加格式化容易拖慢 COMMIT
真正难的不是拼出那条消息,而是让每条日志能准确绑定到它所属的业务上下文、事务边界和执行线程。漏掉 @@SPID 或错用 GETDATE()(而非 SYSUTCDATETIME())都可能让排查变成猜谜。











