• 01-in和not in的使用


    一、前言

    1、最近好多人都在问,in和not in到底走索引吗?

    2、not in的性能怎么样?

    基于上面的2个问题,我们具体的测试一下,实践胜于雄辩。。。

    二、in和not in是否走索引

    1、准备数据

    --1.创建person表
    CREATE TABLE person(
        [id] [int] NULL,
        [name] [nvarchar](50) NULL,
        [age] [int] NULL
    )
    
    --插入数据
    DECLARE @i int = 0;  
    BEGIN TRAN  
    SET @i = 10;  
    WHILE (@i < 10000)  
    BEGIN  
    INSERT INTO customer.dbo.person VALUES (@i, substring(convert(varchar(1000),newid()),2,5),@i /5);  
    SET @i += 1;  
    END;  
    COMMIT TRAN; 
    
    --创建索引
    CREATE NONCLUSTERED INDEX id_index ON person(id)

    2、测试in是否走索引

    注: 该执行计划发生了二次查找(RID Lookup),因为索引里只有id的信息,所以在索引页中查到这条记录后,需要通过RID(有主键通过主键)去数据页里查询其他的字段;

    3、测试not in是否走索引

     这里使用not in后,发生了全表扫描,没有走索引,那么就能确认not in不走索引吗?接着往下看:

     注:这里select后面只查询id字段,因为要是写*的话,not in后面要写好多值,才能走索引,因为查询结果的数据多的话,SQLServer数据库引擎会认为还不如走全表扫描呢。不过这也无所谓,我们只是要测试一下not in是否走索引,所以看到not in发生了全表扫描,不能就认为not in 这个语法不走索引了;

    三、not in的性能测试

    1、再创建一张addressdetail表

    --创建addressdetail表
    CREATE TABLE addressdetail(
        [id] [int] NULL,
        [tname] [nvarchar](20) NULL,
        [depart] [nvarchar](50) NULL,
        [city] [nvarchar](20) NULL
    )
    
    --插入数据
    DECLARE @i int = 0;  
    BEGIN TRAN  
    SET @i = 14000;  
    WHILE (@i < 17000)  
    BEGIN  
    INSERT INTO customer.dbo.addressdetail VALUES (@i, 
    substring(convert(varchar(1000),newid()),2,5),
    substring(convert(varchar(1000),newid()),2,8),
    substring(convert(varchar(1000),newid()),2,3)
    );  
    SET @i += 1;  
    END;  
    COMMIT TRAN; 
    
    --创建索引
    create nonclustered index  ind_id  on addressdetail(id)

    注:2张表(person和addressdetail)的id都允许为null,并且都是索引列

    2、查看not in的执行计划

    select * from person where id not in (select id from addressdetail )

     注:在图中我们可以看到有一个Row Count Spool(Lazy Spool)操作,该操作就是确认addressdetail表中的id列是否有null值(因为该列的属性是允许为null的,所以SQLServer必须额外确认),并且该操作占用的开销也比较多,接近一半的查询成本,因此在这一步是比较浪费性能的;

    3、使用 not exists 代替not in ,对比SQL执行计划的查询开销

    --查询not in的执行计划
    select * from person where id not in (select id from addressdetail )
    
    --使用 not exists 代替not in 查看SQL的执行计划
    select * from person p where  not exists (select * from addressdetail b where p.id=b.id)

    注:由上图可以看出,使用not in的SQL的查询开销是使用not exists的SQL的10倍,仅仅是not in需要确认id列中是否有null值;
    当然这个10倍这个值是不准确的,因为这个和2张表的数据量有关,但是可以肯定的是not in的查询性能确实是多了一步校验null值的步骤,所以会降低性能。

    4、对比not in 和not exists的IO情况 

    --not in
    set  statistics io on
    select * from person where id not in (select id from addressdetail ) 
    
    --not exists
    select * from person p where  not exists (select * from addressdetail b where p.id=b.id)

     注:由上面可以看出not吃掉的IO很高

    5、not in的结果准确性

    由上面的测试,很容易看出使用not in语法是会降低SQL性能的,但是抛去性能的原因,使用not in 还有可能使查询的结果不准确;

    (1)结果不准确主要是和NULL值进行对比的时候,可能会导致结果不准确,我们知道null值并不是一个值,任何与NULL值进行比对的二元操作结果都是null,包括null值本身;

    (2)比对的结果为null时,转换为Bool类型的结果就是False;

    (3)结果不准确的示例

     (4)为什么结果不准确

    --not in 语句
    select * from Test.dbo.teachclass where classnum  not in('303',null)
    
    --not in('303',null) 等价于下面的语句
    select * from Test.dbo.teachclass where classnum<> '303' and classnum<> null

     注:把not in的where条件语句等价用and连接,结果也为null,因为上面说了任何值与null值进行二元操作都为null,并且转换为bool都是false,所以再做and,结果都是false,所以最后没有结果;

    解决办法:使用not Exists作为替代。Exists操作符不会返回null,只会根据子查询中的每一行返回true或者false,当遇到null时,只会返回false,不会因为某个null值导致整个查询返回空。

    四、总结

    1、not in和in走不走索引,是视情况而看的,不能绝对的说in和not不走索引;

    2、对于数据量大的表,使用IN和NOT IN往往效率很低,如果字段值允许为null,还有可能出现结果不准确的情况,所以在尽量避免使用not in;

    3、如果列的属性是not null 的话,是不会产生Row Count Spool(Lazy Spool)操作步骤的;

  • 相关阅读:
    编译原理知识点总括
    操作系统知识概括
    计算机网络(谢希仁)第六版第六章
    计算机网络(谢希仁)第六版第五章
    计算机网络(谢希仁)第六版第三章
    计算机网络(谢希仁)第六版第二章
    计算机网络(谢希仁)第六版第一章
    phpcms v9 模板标签说明整理
    HTML 5终于定稿,八年后我们再一次谈谈怎么改变世界
    jquery toggle 替换的实现
  • 原文地址:https://www.cnblogs.com/jialanyu/p/13498004.html
Copyright © 2020-2023  润新知