必须用sp_executesql而非EXEC传参,因EXEC仅执行字符串、不支持参数化,易致注入;sp_executesql通过显式类型声明的占位符绑定值,隔离数据与代码,并需QUOTENAME()校验标识符。

EXEC 不能直接给拼接的动态 SQL 传参,必须用 sp_executesql ——这是最常被忽略的前提。
为什么不能用 EXEC(@sql) 绑定参数?
因为 EXEC 只做字符串执行,不支持参数化;所有变量都得手动拼进 SQL 字符串里,极易引发 SQL 注入或类型转换错误。比如:DECLARE @table VARCHAR(50) = 'users'; EXEC('SELECT * FROM ' + @table + ' WHERE id = ' + CAST(@id AS VARCHAR))
这里 @id 没有类型保护,空值、特殊字符、注入风险全靠人工过滤。
sp_executesql 的正确调用格式
它接受三部分:SQL 字符串、参数定义字符串、参数值列表。关键点是——参数定义和值必须一一对应,且类型要显式声明:
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM users WHERE status = @status AND created_at > @since'; DECLARE @params NVARCHAR(MAX) = N'@status TINYINT, @since DATETIME'; EXEC sp_executesql @sql, @params, @status = 1, @since = '2026-01-01';
-
@sql中的占位符(如@status)必须和@params里声明的名称、类型完全一致 - 参数值必须按顺序或显式命名传入,不能省略未使用的参数
-
N前缀不能丢,否则 Unicode 字符可能乱码或截断
表名/列名等标识符怎么安全传参?
它们不能走参数绑定,只能拼接,但必须严格校验:
- 用
QUOTENAME()包裹用户输入的表名、列名,自动加方括号并转义特殊字符:QUOTENAME(@tablename)→[user.orders] - 禁止直接拼接未清洗的字符串,尤其来自前端或日志字段
- 白名单校验更稳妥:比如只允许
@action IN ('insert', 'update', 'select'),再映射到对应 SQL 模板
存储过程中动态 SQL 的典型陷阱
常见报错如 Must declare the scalar variable "@xxx" 或 Incorrect syntax near '@',基本都源于这几点:
-
@sql字符串没用N前缀,导致参数占位符识别失败 -
@params里写了@status INT,但调用时传了@status = '1'(字符串 vs 整型) - 拼接标识符时漏掉
QUOTENAME(),遇到带点、空格、连字符的名称直接语法错误 - 动态 SQL 内部用了局部变量(如
@temp_id),但在sp_executesql外部声明 —— 它们作用域不穿透
真正难的不是写出来,而是让拼接和绑定两套逻辑互不干扰,又不出安全漏洞。每次拼标识符,都要问一句:这个字符串是否可能来自不可信来源?

















