必须用sp_executesql代替exec,因exec直接拼接字符串会使用户输入变为可执行代码,而sp_executesql在语法解析阶段绑定参数,确保值仅作数据处理;动态对象名须白名单校验,参数须强类型声明并严格对应。

SQL Server 必须用 sp_executesql,不能用 EXEC(@sql)
直接拼字符串再 EXEC 是高危操作:用户输入会变成可执行代码,@name = 'admin''; DROP TABLE users; --' 这类输入能直接触发注入。而 sp_executesql 在语法解析阶段就绑定参数,值永远被当数据处理,不参与 SQL 结构构建。
常见错误是把值硬塞进字符串:WHERE name = ''' + @name + ''''——这既破坏执行计划缓存,又绕过类型校验,还可能因单引号、NULL 或空格导致语法错误。
- 所有可选参数(如
@user_id、@status、@start_date)声明为NULL允许输入 -
@sql字符串只拼结构:IF @user_id IS NOT NULL SET @sql += ' AND user_id = @user_id' - 参数声明和传值必须严格对应:
N'@user_id INT, @status VARCHAR(20)'后跟@user_id, @status
MySQL 用 PREPARE + EXECUTE USING,别碰 CONCAT() 拼值
MySQL 不支持像 SQL Server 那样批量传参给 PREPARE,但能用占位符 ? + EXECUTE USING 绑定变量。关键在于:值绝不拼进字符串,只留 ? 占位,顺序必须和 USING 列表一致。
写成 CONCAT('WHERE status = ''', in_status, '''') 是典型翻车点:输入含单引号直接报错;整型参数漏判 NULL 会导致 AND id = NULL 永远不成立(因为 NULL = NULL 返回 UNKNOWN);隐式转换还可能让索引失效。
- 每个条件单独判断:
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,', '')); -
EXECUTE stmt USING @user_id, @status, @start_time;—— 顺序错一位就查不到数据
PostgreSQL 要拆开处理:字段名白名单 + 值参数化
PostgreSQL 的 EXECUTE ... USING 只能参数化值,不能参数化字段名或表名。如果让用户传字段名字符串再直接拼接,等于敞开 SQL 注入大门:EXECUTE 'SELECT * FROM orders WHERE ' || in_field || ' = ' USING in_value; 中的 in_field 若为 status; DROP TABLE orders; --,就完了。
安全做法是用 CASE 或 IF 显式限定合法字段,只允许预设的几个字段参与拼接:
IF in_filter_type = 'status' THEN filter_sql := 'status = $1';ELSIF in_filter_type = 'amount' THEN filter_sql := 'amount > $1';- 再用
format()拼主 SQL:EXECUTE format('SELECT * FROM orders WHERE %s', filter_sql) USING in_value;
WHERE 1=1 看似省事,实则掩盖真正问题
WHERE 1=1 本身无害,但它常被当作“免判断开头 AND”的捷径,进而纵容把用户输入直接拼进后续字符串。它解决不了空值逻辑、类型隐式转换、执行计划失效这些底层问题。
真正起作用的是两件事:结构部分(列名、表名)靠白名单或元数据校验;数据部分(查询值)必须走参数化。MySQL 里 QUOTE() 加 REPLACE() 做转义、SQL Server 里 QUOTENAME() 包裹标识符,都只是辅助手段——核心防线仍是参数化执行。
多字段动态搜索最易被忽略的点:NULL 条件默认跳过,但 LIKE 查询要手动加 CONCAT("%", p_name, "%");日期范围要区分 BETWEEN 和 >= / ;布尔字段别用 <code>= 1 而该用 IS TRUE。这些细节不统一,查出来的结果就不可靠。











