应使用参数化动态SQL:SQL Server用sp_executesql传参、MySQL用PREPARE+USING绑定值并白名单校验标识符、PostgreSQL用EXECUTE...USING配合format('%I')转义标识符,所有用户输入值不得拼入SQL字符串,空值条件需显式跳过而非生成无效谓词。

SQL Server 里用 sp_executesql 拼接 WHERE 条件,别用 EXEC()
直接执行拼接 SQL 的话,EXEC('SELECT * FROM t WHERE '+@cond) 看似简单,但参数无法复用、执行计划不缓存、还容易被注入。真正该用的是 sp_executesql:它支持参数化,SQL Server 能识别相同结构的语句并复用执行计划。
常见错误是把所有条件都硬塞进字符串里,比如 @sql = 'WHERE name = '''+@name+''' AND status = '+@status —— 这既危险又低效。
- 所有用户输入值必须作为参数传入
sp_executesql,不能拼进 SQL 字符串 - 动态 WHERE 子句只拼接「条件片段」,比如
AND name = @name,然后统一传参 - 空值或未填搜索项要提前判断,避免生成
AND name = NULL(这永远不成立) - 示例:
SET @sql = 'SELECT * FROM users WHERE 1=1' + @where_clause; EXEC sp_executesql @sql, N'@name NVARCHAR(50), @status INT', @name=@name, @status=@status;
MySQL 动态条件拼接得靠 CONCAT() + PREPARE,但参数绑定受限
MySQL 没有等价于 sp_executesql 的原生参数化动态执行机制,PREPARE 只能绑定变量,不能绑定列名或操作符。所以 WHERE 字段名、比较符(LIKE/=/IN)必须拼进去,但值必须用 ? 占位 + EXECUTE ... USING 传参。
典型翻车点:把用户可控的字段名直接拼进 SQL,比如 CONCAT('WHERE ', @col, ' = ?') —— 如果 @col 是 'id OR 1=1',就完蛋。
- 字段名、表名、操作符必须白名单校验,不能直接拼接用户输入
- 值一律用
?占位,USING后只传变量,不拼字符串 - 多个可选条件建议用 IF 判断逐个追加,而不是堆一堆
OR (@name IS NULL OR name = @name)(全表扫描风险高) - 示例:
SET @sql = CONCAT(@sql, ' AND status = ?'); SET @stmt = CONCAT('SELECT * FROM orders WHERE 1=1', @sql); PREPARE stmt FROM @stmt; EXECUTE stmt USING @status;
PostgreSQL 用 format() 和 EXECUTE ... USING 更安全,但注意权限和计划缓存
PostgreSQL 的 EXECUTE 支持完整参数化:字段名可用 %I(标识符转义),值用 %L 或更推荐的 USING 绑定。比 MySQL 安全得多,也比 SQL Server 的字符串拼接直观。
但容易忽略两点:一是函数内动态 SQL 默认以定义者权限运行,若存储过程用超级用户创建,可能越权访问;二是每次 EXECUTE 都会触发新计划生成,高频调用时不如静态 SQL 快。
- 字段/表名一律用
format('%I', var_name),避免 SQL 注入 - 值优先走
USING,不用format('%L', val)(后者会多一次字符串转义,且无法利用计划缓存) - 如果条件组合有限(比如最多 8 种筛选组合),考虑拆成多个静态分支,反而更快更稳
- 示例:
sql := format('SELECT * FROM product WHERE 1=1 %s', where_clause); EXECUTE sql USING p_name, p_category;
WHERE 条件为空时别留 WHERE 1=1,也别漏掉 NULL 处理逻辑
几乎所有动态拼接方案开头都加 WHERE 1=1 图省事,但这会让执行计划无法跳过 WHERE 优化,尤其在 PostgreSQL 中可能影响索引选择。更麻烦的是,很多人忘了处理 NULL 输入:前端没传 name,后端收到 @name = NULL,直接拼 AND name = @name,结果查不到任何数据。
- 空条件建议跳过整个子句,而不是拼
AND 1=1或AND (1=1) -
NULL输入应转为「忽略该条件」,不是「匹配 NULL 值」;用IS NULL判断参数,而不是依赖数据库默认行为 - 模糊搜索(
LIKE)记得补%,但别写成name LIKE '%'+@name+'%'—— 应该在应用层或参数前处理,否则无法走索引 - 日期范围条件要小心
BETWEEN的闭区间特性,起止时间没填全时,别让BETWEEN NULL AND @end把整张表拖垮
动态拼接最深的坑不在语法,而在「以为条件可选,其实数据库不认空值语义」——NULL 参数、空字符串、零值,每种都要单独想好跳过逻辑,而不是指望 SQL 自动忽略。










