SQL SERVER中关于OR会导致索引扫描或全表扫描的浅析

原文:SQL SERVER中关于OR会导致索引扫描或全表扫描的浅析

在SQL SERVER的查询语句中使用OR是否会导致不走索引查找(Index Seek)或索引失效(堆表走全表扫描 (Table Scan)、聚集索引表走聚集索引扫描(Clustered Index Seek))呢?是否所有情况都是如此?又该如何优化呢? 下面我们通过一些简单的例子来分析理解这些现象。下面的实验环境为SQL SERVER 2008,如果在不同版本有所区别,欢迎指正。

堆表单索引

首先我们构建我们测试需要实验环境,具体情况如下所示:

DROP TABLE TEST
   

CREATE TABLE TEST (OBJECT_ID  INT, NAME VARCHAR(32));

 

CREATE INDEX PK_TEST ON TEST(OBJECT_ID)

   

DECLARE @Index INT =0;

 

WHILE @Index < 500000

BEGIN

    INSERT INTO TEST

    SELECT @Index, ‘kerry‘+CAST(@Index AS VARCHAR(6));

   

    SET @Index = @Index +1;

END

 

 

UPDATE STATISTICS TEST WITH FULLSCAN

场景1:如下所示,并不是所有的OR条件都会导致SQL走全表扫描。具体情况具体分析,不要套用教条。

SELECT * FROM TEST WHERE (OBJECT_ID =5 OR OBJECT_ID = 105)

场景2:加了条件1=1后,执行计划从索引查找(Index Seek)变为全表扫描(Table Scan),为什么会如此呢?个人理解为优化器将OR运算拆分为两个子集处理,由于一些原因,1=1这个条件导致优化器认定需要全表扫描才能完成1=1条件子集的计算处理(为了理解这个,煞费苦心,鉴于理论薄弱,如有错误或不足,敬请指出)。所以优化器在权衡代价后生成的执行计划最终选择了全表扫描(Table Scan)

SELECT * FROM TEST WHERE (1=1 OR OBJECT_ID =105);

场景3: 下面场景比较好理解,因为下面需要从500000条记录中取出499700条记录,而全表扫描(Table Scan)肯定是最优的选择,代价(Cost)最低。

SELECT * FROM TEST WHERE (OBJECT_ID >300 OR OBJECT_ID =105); 

场景4:这种场景跟场景2的情况本质是一样的。所以在此略过。其实类似这种写法也是实际情况中最常出现的情况,还在迷糊的同学,赶紧抛弃这种写法吧

DECLARE @OBJECT_ID INT =150;
 

SELECT * FROM TEST WHERE (@OBJECT_ID IS NULL OR OBJECT_ID [email protected]_ID);

聚集索引表单索引

在聚集索引表中,我们也依葫芦画瓢,准备实验测试的数据环境。

DROP TABLE TEST
   

CREATE TABLE TEST (OBJECT_ID  INT, NAME VARCHAR(32));

 

CREATE CLUSTERED INDEX PK_TEST ON TEST(OBJECT_ID)

   

DECLARE @Index INT =0;

 

WHILE @Index < 500000

BEGIN

    INSERT INTO TEST

    SELECT @Index, ‘kerry‘+CAST(@Index AS VARCHAR(6));

   

    SET @Index = @Index +1;

END

 

 

UPDATE STATISTICS TEST WITH FULLSCAN

场景1 :索引查找(Index Seek)

SELECT * FROM TEST WHERE (OBJECT_ID =5 OR OBJECT_ID = 105)

场景2:聚集索引扫描(Clustered Index Scan)

场景3:似乎与堆表有所不同。聚集索引表居然还是走聚集索引查找。

场景4:OR导致聚集索引扫描

如果堆表或聚集索引表上建立有联合索引,情况也大致如此,在此不做过多案例讲解。下面仅仅讲述一两个案例场景。

DROP TABLE test1; 
 

CREATE TABLE test1 

  ( 

     a INT, 

     b INT, 

     c INT, 

     d INT, 

     e INT 

  ) 

 

DECLARE @Index INT =0; 

 

WHILE @Index < 10000 

  BEGIN 

      INSERT INTO test1 

      SELECT @Index, 

             @Index, 

             @Index, 

             @Index, 

             @Index 

 

      SET @Index = @Index + 1; 

  END 

 

CREATE INDEX idx_test_n1 

  ON test1(a, b, c, d) 

 

UPDATE STATISTICS test1 WITH fullscan; 

SELECT * FROM TEST1 WHERE A=12 OR B> 500 OR C >100000

因为结果集是几个条件的并集,最多只能在查找A=12的数据时用索引,其它几个条件都需要表扫描,那优化器就会选择直接走一遍表扫描,以最低的代价COST完成,所以索引就失效了。

那么如何优化查询语句含有的OR的SQL语句呢?方法无外乎有三种:

1:通过索引覆盖,使包含OR的SQL走索引查找(Index Seek)。但是这个只能满足部分场景,并不能解决所有这类SQL。这个Solution具有一定的局限性。

SELECT * FROM TEST1 WHERE A=12 OR B=500

如果我们通过索引覆盖,在字段B上面也建立索引,那么下面OR查询也会走索引查找。

CREATE INDEX IDX_TEST1_B ON TEST1(B);
 

SELECT * FROM TEST1 WHERE A=12 OR B=500 

2:使用IN替换OR。 但是这个Solution也有很多局限性。在此不做过多阐述。

3:一般将OR的字句分解成多个查询,并且通过UNION ALL 或UNION连接起来。在联合索引或有索引覆盖的场景下。大部分情况下,UNION ALL的效率更高。但是并不是所有的UNION ALL都会比OR的SQL的代价(COST),特殊的情况或特殊的数据分布也会出现UNION ALL比OR代价要高的情况。例如,上面特殊的要求,从全表中取两条记录,如下所示

SELECT * FROM TEST1 WHERE A=12
 

UNION ALL

 

SELECT * FROM TEST1 WHERE B=500 

UNON ALL语句的代价(Cost)要高与OR是因为它做了两次索引查找(Index Seek),而OR语句只做一次索引查找(Index Seek)就完成了。开销明显小一些,但是实际情况这类特殊情况比较少,实际情况的取数条件、数据都比这个简单案例要复杂得多。所以在大部分情况下,拆分为UNION ALL语句的效率要高于OR语句

另外一个案例,就是最上面实验的堆表TEST, 在字段OBJECT_ID上建有索引

SELECT * FROM TEST WHERE (OBJECT_ID >300 OR OBJECT_ID =105);
 

SELECT * FROM TEST WHERE OBJECT_ID >300

 

UNION ALL

 

SELECT * FROM TEST WHERE OBJECT_ID =105;

可以从下面看出两者开销不同的地方在于IO方面,两者开销之所以有区别,是因为第二个SQL多了一次扫描(索引查找)

总结:

在实际开发环境中,OR这种写法确实会带来很多不确定性,尽量使用UNION 或IN替换OR。我们需要遵循一些规则,但是也不能认为它就是一成不变的,永为真理。具体场景、具体环境具体分析。要知其然知其所以然。在微软亚太区数据库技术支持组的官方博客中就有一个案例SQL Server性能问题案例解析 (3)也是OR引起的性能案例。 博客中有个观点,我觉得挺赞的:”需要注意的是,对于OR或UNION,并没有确定的孰优孰劣,使用时要进行测试才能确定。“ 。

时间: 2024-12-29 07:50:15

SQL SERVER中关于OR会导致索引扫描或全表扫描的浅析的相关文章

SQL SERVER中什么情况会导致索引查找变成索引扫描

SQL Server 中什么情况会导致其执行计划从索引查找(Index Seek)变成索引扫描(Index Scan)呢? 下面从几个方面结合上下文具体场景做了下测试.总结.归纳. 1:隐式转换会导致执行计划从索引查找(Index Seek)变为索引扫描(Index Scan) Implicit Conversion will cause index scan instead of index seek. While implicit conversions occur in SQL Serve

SQL Server中TOP子句可能导致的问题以及解决办法

原文:SQL Server中TOP子句可能导致的问题以及解决办法 简介      在SQL Server中,针对复杂查询使用TOP子句可能会出现对性能的影响,这种影响可能是好的影响,也可能是坏的影响,针对不同的情况有不同的可能性.      关系数据库中SQL语句只是一个抽象的概念,不包含任何逻辑.很多元数据都会影响执行计划的生成,SQL语句本身并不作为生成执行计划所参考的元数据(提示除外),但TOP关键字却是直接影响执行计划的一个关键字,因此在某些情况下使用TOP会导致性能受到影响,下面我们来

SQL Server 中SELECT INTO 和 INSERT INTO SELECT 两种表复制语句

1.INSERT INTO SELECT语句 语句形式为:Insert into Table2(field1,field2,...) select value1,value2,... from Table1 要求目标表Table2必须存在,由于目标表Table2已经存在,所以我们除了插入源表Table1的字段外,还可以插入常量. 2.SELECT INTO FROM语句 语句形式为:SELECT vale1, value2 into Table2 from Table1 要求目标表Table2不

SQL Server临界点游戏——为什么非聚集索引被忽略!

当我们进行SQL Server问题处理的时候,有时候会发现一个很有意思的现象:SQL Server完全忽略现有定义好的非聚集索引,直接使用表扫描来获取数据.我们来看看下面的表和索引定义: 1 CREATE TABLE Customers 2 ( 3 CustomerID INT NOT NULL, 4 CustomerName CHAR(100) NOT NULL, 5 CustomerAddress CHAR(100) NOT NULL, 6 Comments CHAR(185) NOT NU

SQL Server中是否可以准确获取最后一次索引重建的时间?

原文:SQL Server中是否可以准确获取最后一次索引重建的时间? 在SQL Server中,我们能否找到索引的创建时间?最后一次索引重建(Index Rebuild)的时间? 最后一次索引重组(INDEX REORGANIZE)的时间呢?  答案是我们无法准确的找到索引的创建时间.最后一次索引重组时间,最后一次索引重建的时间. 其实就目前SQL Server的各个版本而言,还没有一个系统表或DMV视图有保存索引创建的时间,索引重建的时间.索引重组的时间.但是有些方法可以间接得到最后一次索引重

在SQL Server中快速删除重复记录

在SQL Server中快速删除重复记录 2006-07-17 21:53:15 分类: SQL Server 开发人员的噩梦——删除重复记录 想必每一位开发人员都有过类似的经历,在对数据库进行查询或统计的时候不时地会碰到由于表中存在重复的记录而导致查询和统计结果不准确.解决该问题的办法就是将这些重复的记录删除,只保留其中的一条. 在SQL Server中除了对拥有十几条记录的表进行人工删除外,实现删除重复记录一般都是写一段代码,用游标的方法一行一行检查,删除重复的记录.因为这种方法需要对整个表

T-SQL查询进阶--SQL Server中的事务与锁

为什么需要锁 在任何多用户的数据库中,必须有一套用于数据修改的一致的规则,当两个不同的进程试图同时修改同一份数据时,数据库管理系统(DBMS)负责解决它们之间潜在的冲突.任何关系数据库必须支持事务的ACID属性,所以在开始了解锁之前,首先简单了解一下数据库事务和事务的ACID属性. 原子性(Atomicity):原子性意味着数据库中的事务执行是作为原子.即不可在分,整个语句要么执行,要么不执行 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏.(唯一约

SQL Server中LIKE %search_string% 走索引查找(Index Seek)浅析

  在SQL Server的SQL优化过程中,如果遇到WHERE条件中包含LIKE '%search_string%'是一件非常头痛的事情.这种情况下,一般要修改业务逻辑或改写SQL才能解决SQL执行计划走索引扫描或全表扫描的问题.最近在优化SQL语句的时候,遇到了一个很有意思的问题.某些使用LIKE '%' + @search_string + '%'(或者 LIKE @search_string)这样写法的SQL语句的执行计划居然走索引查找(Index Seek).下面这篇文章来分析一下这个

为什么SQL语句Where 1=1 and在SQL Server中不影响性能

    最近一个朋友和我探讨关于Where 1=1 and这种形式的语句会不会影响性能.最后结论是不影响.     虽然结论正确,但对问题的认识却远远没有解决问题的根本.实际上在T-SQL语句的书写过程中经常犯得错误就是得出一个很窄的结论,然后教条式的奉若圣经,对于T-SQL领域来说,在网上经常可以看到所谓的优化守则,随便在网上搜了一些摘录如下: 不要有超过5个以上的表连接(JOIN) 考虑使用临时表或表变量存放中间结果 少用子查询 视图嵌套不要过深,一般视图嵌套不要超过2个为宜. 对出现在wh