sp_executesql是SQL Server中唯一安全支持参数化动态查询的系统存储过程,它通过分离SQL语句与参数值,有效防止SQL注入,并支持执行计划缓存与复用;其调用格式为EXEC sp_executesql @stmt, @params, @param1, @param2...,其中@stmt必须为nvarchar类型且仅含占位符,@params声明参数类型,后续实参须严格按顺序、类型、数量及OUTPUT标识匹配。

为什么不能直接拼接字符串执行动态SQL
直接用 + 拼接用户输入进 EXEC() 会引发严重SQL注入风险,比如传入 ' OR 1=1 -- 就可能绕过条件检查。SQL Server 的 sp_executesql 是唯一安全支持参数化动态查询的系统存储过程,它把参数定义和值分离,让 SQL Server 引擎能预编译、缓存执行计划,还能复用。
sp_executesql 的三个核心参数怎么填
调用格式固定为:EXEC sp_executesql @stmt, @params, @param1, @param2, ...。三部分缺一不可:
-
@stmt:必须是nvarchar类型(哪怕语句里全是ASCII字符),内容是纯SQL字符串,**不能包含变量值,只留参数占位符**,例如N'SELECT * FROM users WHERE id = @id' -
@params:也是nvarchar,声明所有占位符的类型,格式像N'@id int, @name nvarchar(50)',类型必须和后续实参严格匹配(int不能写成integer,nvarchar必须带长度) - 后续实参:按声明顺序传入变量,类型、数量、顺序必须和
@params完全一致;如果是输出参数,需加OUTPUT关键字
输出参数和返回值怎么拿
如果动态SQL里用了 SELECT COUNT(*) 或需要返回单个值,别用 RETURN(它只支持整数且无法在 sp_executesql 中捕获),改用输出参数:
DECLARE @sql nvarchar(max) = N'SELECT @cnt = COUNT(*) FROM orders WHERE status = @status'; DECLARE @params nvarchar(max) = N'@status tinyint, @cnt int OUTPUT'; DECLARE @count int; EXEC sp_executesql @sql, @params, @status = 1, @cnt = @count OUTPUT; -- 此时 @count 已被赋值
注意:@cnt 在 @params 里声明为 OUTPUT,调用时也必须显式写 @cnt = @count OUTPUT,漏掉 OUTPUT 关键字会导致变量始终为 NULL。
常见报错和踩坑点
以下错误几乎都源于参数契约不一致:
-
Must declare the scalar variable "@xxx":语句中写了@xxx,但@params没声明,或声明了却没在调用时传值 -
Procedure expects parameter '@params' of type 'ntext/nchar/nvarchar':传了varchar或char给@stmt或@params,必须用nvarchar - 结果集列名丢失或类型异常:动态SQL里用了
SELECT *,但表结构变更后列顺序/类型变化,建议明确写出字段名并用AS固定别名 - 性能没提升:如果每次生成的
@stmt字符串都不同(比如含硬编码时间戳),SQL Server 无法重用执行计划,应尽量让语句模板稳定
真正难的是保持参数声明、占位符、实参三者完全对齐——少一个字母、错一个长度、漏一个 OUTPUT,都会失败,而且错误提示往往不直接指向根源。

















