sp_executesql是防sql注入的硬性底线,因其参数在语法解析后绑定、被当纯数据处理;exec(@sql)则直接执行拼接字符串,使用户输入成为可执行代码。

SQL Server 里用 sp_executesql 拼接动态 WHERE 最稳妥
硬拼字符串 + EXEC() 看似简单,但参数无法复用、容易 SQL 注入、执行计划缓存失效。必须用 sp_executesql 配合参数化变量。
常见错误是把参数值直接拼进 SQL 字符串里,比如:'WHERE name = ''' + @name + '''' —— 这既不安全,也无法利用查询计划缓存。
- 所有可选条件字段(如
@user_id、@status、@start_date)都声明为存储过程输入参数,允许 NULL - 构建 SQL 字符串时只拼接条件结构,不拼接值:
IF @user_id IS NOT NULL SET @sql += ' AND user_id = @user_id' - 最后调用
sp_executesql @sql, N'@user_id INT, @status VARCHAR(20), ...', @user_id, @status, ...
MySQL 存储过程中避免用 CONCAT() 直接拼条件值
MySQL 不支持像 SQL Server 那样传参数列表给 PREPARE,但可以绕过:把所有条件参数统一用占位符 ?,再用 EXECUTE ... USING 绑定变量。
典型坑是写成 CONCAT('WHERE status = ''', in_status, '''') —— 输入含单引号就报错,且无法走索引(隐式类型转换或全表扫描风险更高)。
- 每个条件单独判断是否启用:
IF in_user_id IS NOT NULL THEN SET @sql = CONCAT(@sql, ' AND user_id = ?'); - 把所有非 NULL 参数按顺序塞进变量列表:
SET @params = CONCAT(@params, IF(in_user_id IS NOT NULL, '@user_id,', ''), IF(in_status IS NOT NULL, '@status,', '')); -
EXECUTE stmt USING @user_id, @status, @start_time;—— 注意顺序必须和?出现顺序严格一致
PostgreSQL 用 format() + USING 实现安全动态 WHERE
PostgreSQL 的 EXECUTE ... USING 支持参数绑定,但 WHERE 子句里的字段名不能参数化(只能是值),所以字段名仍需拼接 —— 这部分必须白名单校验。
错误做法是让用户传入字段名字符串然后直接拼:EXECUTE 'SELECT * FROM orders WHERE ' || in_field || ' = $1' USING in_value; —— 字段名未过滤等于开放 SQL 注入入口。
- 先用
CASE或IF显式限定合法字段:IF in_filter_type = 'status' THEN filter_sql := 'status = $1'; ELSIF in_filter_type = 'amount' THEN filter_sql := 'amount > $1'; END IF; - 用
format()拼接主 SQL:EXECUTE format('SELECT * FROM orders WHERE %s', filter_sql) USING in_value; - NULL 条件默认跳过,不生成对应子句;空字符串值也建议转为 NULL 处理,避免意外匹配
WHERE 条件多时注意执行计划失效和 OR 陷阱
动态拼出来的 SQL,哪怕参数相同,只要字符串有细微差异(比如空格数、换行、字段顺序不同),SQL Server 就算作新语句,不复用执行计划。更隐蔽的问题是 OR 写法让优化器放弃索引。
例如:WHERE (@user_id IS NULL OR user_id = @user_id) 看似简洁,但绝大多数情况下导致全表扫描 —— 即使 @user_id 有值。
- 优先用
AND拼接独立条件,而不是在 WHERE 里堆OR判断参数是否为空 - 对高频变化的参数(如分页偏移量),考虑加
OPTION (RECOMPILE)(SQL Server)强制重编译,避免“参数嗅探”拖慢首次执行 - PostgreSQL 中,
IN列表超过 100 项建议拆成临时表 JOIN,否则 planner 可能选错路径
真正麻烦的不是怎么拼字符串,而是字段名能不能由用户控制、NULL 怎么语义化、以及每次生成的 SQL 是否稳定可缓存 —— 这些点漏掉一个,上线后性能抖动就很难排查。











