必须禁用exec(@sql),因其将用户输入直接拼入sql字符串导致注入;sp_executesql通过参数化隔离数据与代码,配合quotename()处理对象名、白名单校验及严格参数声明,方可保障动态sql安全。

SQL Server 中只该用 sp_executesql,别碰 EXEC(@sql) —— 后者等于把 SQL 注入大门焊死在 open 状态。
为什么 EXEC(@sql) 必须禁用
它不认外部变量,拼接时直接把用户输入塞进字符串里,毫无隔离。比如:
SET @sql = 'SELECT * FROM users WHERE name = ''' + @name + '''';
一旦 @name 是 'admin'' OR 1=1 --',整条语句就变成:
SELECT * FROM users WHERE name = 'admin' OR 1=1 --'
——查出所有用户,还注释掉后续逻辑。
-
EXEC(@sql)报错Msg 137, Level 15, State 2: 必须声明标量变量 "@id"是常态,因为变量作用域根本进不去字符串内部 - 执行计划无法复用:每次拼出来的字符串不同,SQL Server 当成全新语句编译
- 没法做参数类型校验,
@price传个字符串进去也不会报错,运行时才炸
sp_executesql 的正确调用姿势
核心就三点:参数声明必须是 NVARCHAR(MAX)、值类变量全走参数列表、标识符(表名/列名)必须 QUOTENAME() 包裹。
- 参数声明写成
N'@status INT, @start_time DATETIME',不能用VARCHAR,否则超长截断直接语法错误 - WHERE 条件里的值一律不拼字符串:
N' AND status = @status',然后在第三个及之后的参数位置传@status值 - 表名不能参数化,但必须转义:
N'SELECT * FROM ' + QUOTENAME(@table_name),QUOTENAME自动加[]并处理单引号、分号等 - 调用时参数顺序必须和声明顺序严格一致,
@status写在声明第一位,传参时就得放第一个位置
动态 WHERE 条件拼接的防错写法
别写 IF @name IS NOT NULL SET @sql += ' AND name = @name',空条件会导致 WHERE AND ... 语法错误。
- 起手统一写
N'SELECT * FROM orders WHERE 1=1',SQL Server 优化器会忽略它,不影响性能 - 每个条件都以
AND开头:IF @status IS NOT NULL SET @sql += N' AND status = @status' - LIKE 模糊匹配,% 一定加在参数里:
SET @name_param = N'%' + @name + N'%',再传给@name_param参数 - 日期范围单独判断:
IF @start_date IS NOT NULL SET @sql += N' AND created_at >= @start_date'
MySQL 和 Oracle 的关键差异点
它们没有 sp_executesql,但思路一致:值绑定 + 标识符白名单/转义。
- MySQL 用
PREPARE/EXECUTE,占位符只支持值(?),表名必须拼接,得自己用CONCAT('`', REPLACE(@col, '`', '``'), '`')转义 - Oracle 用
EXECUTE IMMEDIATE,值用USING绑定,标识符拼接前建议先走白名单:CASE WHEN @table_name IN ('orders','logs') THEN @table_name ELSE NULL END - GBase 8a 和 SQL Server 类似,但需确认是否支持
sp_executesql;若不支持,只能靠严格输入校验+字符串拼接
最易被忽略的是:标识符拼接那一步,没人会真去测 @table_name = 'users; DROP TABLE users--' 这种输入,但攻击者会。QUOTENAME 或手动转义不是可选项,是保命线。










