必须用sp_executesql代替exec,严格声明参数类型并白名单校验动态对象名;mysql用prepare+execute using,postgresql用format()+execute using+字段白名单,三者均禁用字符串拼接值。

直接拼字符串 + EXEC(@sql) 是最危险的写法,SQL Server 里必须用 sp_executesql 配合显式参数声明;MySQL 和 PostgreSQL 则需分别用 EXECUTE ... USING 和 EXECUTE format() ... USING,否则必然面临 SQL 注入或执行计划失效。
SQL Server:用 IF 拼接 + sp_executesql + OPTION(RECOMPILE)
动态条件本质是“有就加,没有就跳过”,硬拼字符串比强塞 OR 逻辑更可控,但必须守住三道防线:
-
sp_executesql是唯一安全入口,EXEC(@sql)禁用 - 每个参数类型必须显式声明,且和传入变量完全一致(
@tenant_id INT不能传字符串'123') - 多条件组合变化大时,结尾加
OPTION (RECOMPILE),避免首次编译计划污染后续查询 - 拼接时开头带空格的
AND,结尾不加多余空格,防止语法错误
示例片段:
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM orders WHERE 1=1'; IF @tenant_id IS NOT NULL SET @sql += N' AND tenant_id = @tenant_id'; IF @status IS NOT NULL SET @sql += N' AND status = @status'; EXEC sp_executesql @sql, N'@tenant_id INT, @status VARCHAR(20)', @tenant_id, @status OPTION (RECOMPILE);
MySQL:用 PREPARE + EXECUTE USING + 占位符 ?
MySQL 不支持 sp_executesql 那样的参数列表绑定,必须用 ? 占位符 + EXECUTE ... USING,且顺序必须严格匹配:
一款AI数据处理工具,主要用于用于查询 Massive 市场数据端点的 Bash CLI 封装和 OpenClaw 技能,适用于 Codex 或 OpenClaw 代理从 shell 调用,适合需要提升相关任务效率的用户。
- 所有条件字段名仍需拼接进 SQL 字符串,但值一律用
?,禁止CONCAT('WHERE status = ''', in_status, '''') -
USING后变量顺序必须和?出现顺序完全一致,错一位就查错数据 - NULL 条件跳过,不拼该段;若想保留索引 Seek 能力,也可改用
(in_status IS NULL OR status = in_status)写法,但注意 MySQL 5.7 对这种写法优化不佳
关键代码节选:
SET @sql = 'SELECT * FROM orders WHERE 1=1'; IF in_status IS NOT NULL THEN SET @sql = CONCAT(@sql, ' AND status = ?'); END IF; IF in_user_id IS NOT NULL THEN SET @sql = CONCAT(@sql, ' AND user_id = ?'); END IF; PREPARE stmt FROM @sql; EXECUTE stmt USING in_status, in_user_id;
PostgreSQL:用 format() + EXECUTE USING + 白名单校验字段名
PostgreSQL 支持 EXECUTE ... USING 绑定值,但字段名无法参数化——这部分必须白名单校验,否则等于开放注入入口:
- 禁止直接拼用户传入的字段名:
EXECUTE 'SELECT * FROM t WHERE ' || in_field || ' = $1'是高危操作 - 用
CASE或IF显式限定合法字段:WHEN in_filter_type = 'amount' THEN 'amount > $1' - 再用
format()拼主 SQL:EXECUTE format('SELECT * FROM orders WHERE %s', filter_sql) USING in_value - 排序字段同理,不能靠传
'ORDER BY ' || sort_col,得用CASE分支控制
别硬扛:固定逻辑优先用 ITVF 或 SQL Macro
如果过滤条件组合稳定(比如永远按租户+状态查订单),就别在存储过程里反复拼 SQL:
- SQL Server 推荐内联表值函数(ITVF):
CREATE FUNCTION dbo.fn_orders(@tenant_id INT, @status VARCHAR(20)) RETURNS TABLE...,调用干净、执行计划稳定 - Oracle 21c 可用
SQL_MACRO(SCALAR)封装 WHERE 片段,编译时内联展开,无运行时解析开销,但表名列名必须写死,只接受标量参数 - 视图不能传参,CTE 不能带参数,这些都不是替代方案
真正容易被忽略的是:字段名拼接那部分永远要白名单校验,无论数据库类型;而 OPTION (RECOMPILE) 看似重,实则比缓存一个错的执行计划便宜得多——尤其当条件区分度低、统计信息滞后时,它才是兜底的那根保险丝。










