OR条件本身不慢,但常致优化器弃用索引而选全表扫描;因多列OR难SARGable、基数估计偏差及统计信息滞后,推荐UNION ALL拆分或IN替代同列OR,并定期更新统计信息。

直接结论:OR 条件本身不慢,但多数情况下会让优化器放弃使用索引查找,转而走全表扫描或低效的索引扫描——这不是语法问题,而是执行计划选择问题。
为什么 OR 会让执行计划变差
SQL Server 优化器对 OR 的处理依赖于谓词是否“SARGable”(可搜索参数)。当多个条件用 OR 连接且涉及不同列(如 WHERE Status = 'A' OR Type = 'X'),优化器通常无法同时高效利用两个列上的独立索引。它可能退化为:单列索引扫描 + 过滤,或干脆走聚集索引扫描。
更隐蔽的问题是统计信息偏差:OR 合并的基数估计值常被高估(尤其在直方图不重叠时),导致优化器误判数据量,选错连接方式或内存授予。
- 常见错误现象:
SELECT * FROM Orders WHERE CustomerID = 123 OR OrderDate >= '2025-01-01'在千万级表上耗时 8 秒,而拆成两个UNION ALL查询各只要 0.2 秒 - 典型场景:后台管理界面的多条件模糊筛选(状态/类型/日期任意组合)
- 关键点:不是所有
OR都有问题——同一列上的IN(如Status IN ('A','B','C'))通常能走索引查找,本质是优化器内部做了等价重写
替代方案优先级:从安全到激进
不要一上来就改写逻辑。先确认是否真由 OR 引发性能瓶颈:用 SET STATISTICS XML ON 查看执行计划,重点观察是否有 Index Scan 或 Table Scan 节点,以及 Estimated Number of Rows 是否远大于 Actual Number of Rows。
-
UNION ALL拆分(推荐首选):把OR拆成多个独立查询,各自走最优索引。注意加ALL避免去重开销;若业务允许重复行,这是最稳定、兼容性最好的方案 - 使用
IN替代同列多值OR:如WHERE CategoryID = 1 OR CategoryID = 2 OR CategoryID = 3→ 改为WHERE CategoryID IN (1,2,3),能触发索引查找 - 添加覆盖索引:如果必须保留
OR结构,为所有涉及列建复合索引,并包含 SELECT 列(避免回表)。例如CREATE INDEX IX_Orders_Status_Type_Date ON Orders(Status, Type) INCLUDE(OrderDate, Amount) - 慎用
OPTION(RECOMPILE):仅当参数值分布极不均匀(如 95% 查询只查Status='A',但偶尔查Type='X')时考虑,否则编译开销会反噬性能
存储过程中动态 OR 的陷阱
很多存储过程用 @param IS NULL OR Column = @param 实现“可选参数”,这在 SQL Server 中极易触发参数嗅探失效——首次执行传入 NULL,优化器生成全表扫描计划并缓存,后续非空参数也沿用该计划。
- 正确做法:用
IF分支显式控制逻辑流,而非堆砌OR。例如:IF @CustomerID IS NOT NULL SELECT ... WHERE CustomerID = @CustomerID ELSE SELECT ... WHERE Status = @Status - 若必须用单条语句,改用
OPTION(OPTIMIZE FOR UNKNOWN),但需测试是否真比分支慢 - 绝对避免:
EXEC('SELECT ... WHERE ' + @dynamicWhere)—— 不仅难维护,还彻底关闭计划缓存,且有 SQL 注入风险 - 性能影响:含动态
OR的存储过程在高并发下容易堆积cxpacket等待,CPU 使用率异常升高(参考sys.dm_exec_requests中的wait_type)
最容易被忽略的细节
即使你用了 UNION ALL 或覆盖索引,仍可能因统计信息过期导致计划劣化——特别是 OR 涉及的列更新频繁时。别只盯着写法,定期检查:SELECT OBJECT_NAME(object_id), stats_id, name, STATS_DATE(object_id, stats_id) FROM sys.stats WHERE object_id = OBJECT_ID('Orders')。自动更新阈值在大表上可能滞后数小时,手动更新有时比重构代码更有效。











