应使用结构化参数+union all拆分跨字段or并为每个分支加option(recompile),因sql server优化器难以估算多字段or选择率,易导致索引失效、全表扫描及参数嗅探问题。

直接结论:别拼 @WhereStr,用结构化参数 + UNION ALL 拆分跨字段 OR,并为每个分支单独加 OPTION (RECOMPILE)。
为什么动态拼接 @WhereStr 会让索引失效
SQL Server 面对类似 WHERE name = 'a' OR status = 1 OR type = 'x' 这种跨字段 OR,优化器很难估算各分支选择率,常放弃所有索引,退化为 Table Scan 或 Clustered Index Scan。这不是你没建索引,而是优化器“看不懂”这个逻辑组合。
常见错误现象:sp_executesql 执行慢、STATISTICS IO 显示逻辑读暴增、执行计划里 Estimated Rows 和 Actual Rows 差距巨大、出现 Parallelism 节点。
关键原因:参数嗅探 + 谓词不可SARG化(比如隐式转换、函数包裹字段)。
用 UNION ALL 替代跨字段 OR 的实操规则
拆分不是语法切分,核心是让每个子查询能独立走索引。
- 每个子查询只含一个等值条件(如
WHERE status = @s),且该字段必须是对应索引的最左列 -
SELECT列表、数据类型、顺序必须完全一致,否则UNION ALL报错 - 跳过空值分支:若
@cid为NULL,整个SELECT ... WHERE customer_id = @cid子句应不生成 - 强制重编译:每个子查询末尾加
OPTION (RECOMPILE),避免参数嗅探复用错误计划 - 优先用
UNION ALL;仅当业务明确要求去重时才用UNION(会触发排序+去重,开销翻倍)
示例片段:
SELECT * FROM orders WHERE status = @status OPTION (RECOMPILE) UNION ALL SELECT * FROM orders WHERE customer_id = @cid OPTION (RECOMPILE) UNION ALL SELECT * FROM orders WHERE amount > @min_amount OPTION (RECOMPILE)
存储过程里怎么安全生成这种动态 SQL
不能靠 REPLACE 或正则硬切原始 @WhereStr——括号嵌套、引号、注释里的 OR 都会被误切。
正确路径只有两条:
- 前端/应用层传结构化参数,例如 JSON 数组
[{"field":"status","value":"shipped"},{"field":"customer_id","value":101}],后端按字段逐个构建子查询 - 必须接收原始字符串时,用自定义函数(如
[F_OptimizeOr])解析:识别字段名、操作符、字面值,并过滤掉无效分支(如空值、非法操作符) - 最终 SQL 字符串长度控制在 10MB 内,避免超出
NVARCHAR(MAX)实际限制导致sp_executesql失败 - 所有变量类型必须与目标字段严格一致(如字段是
CHAR(10),就别用DECLARE @id INT去查)
容易被忽略的索引前提和陷阱
再好的 UNION ALL 拆分也救不了没建对的索引。
- 检查执行计划里是否真有
Index Seek;如果只有Index Scan或更差,说明索引没被选中 - 复合索引必须满足最左前缀:索引是
(status, created_date),但查询只写WHERE created_date > '2024-01-01',这个索引基本无效 - 字段上别用函数:
WHERE YEAR(order_date) = 2024→ 改成WHERE order_date >= '2024-01-01' AND order_date - 别删“看起来没用”的索引:先查
sys.dm_db_index_usage_stats,确认user_seeks = 0且user_updates > 0,再排除主键、唯一约束依赖项
真正卡住性能的,往往不是“要不要加索引”,而是“索引建在哪几列”“查询条件有没有对齐最左列”“变量类型有没有悄悄触发隐式转换”。这些细节不盯住,再多的动态 SQL 优化也只是在原地打转。











