• 浅析SQL中 in 与 exists 用法的区别及其各自执行流程、not in/not exists区别、sql优化应该如何选择in还是exists


    一、in 与 exists 的区别

    1、exists、not exists 一般都是与子查询一起使用,In 可以与子查询一起使用,也可以直接in (a,b.....)

    2、exists 会针对子查询的表使用索引,not exists 会对主子查询都会使用索引。in 与子查询一起使用的时候,只能针对主查询使用索引,not in 则不会使用任何索引

      注意:一直以来认为 exists 比 in 效率高的说法是不准确的。

      in 是把外表和内表作 hash 连接,而 exists 是对外表作 loop 循环,每次 loop 循环再对内表进行查询。

      如果查询的两个表大小相当,那么用 in 和 exists 差别不大。

      如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in:

    -- 例如:表A(小表),表B(大表)
    -- 例子1
    select * from A where cc in (select cc from B)
    -- 效率低,用到了A表上cc列的索引;
    
    select * from A where exists(select cc from B where cc=A.cc)
    -- 效率高,用到了B表上cc列的索引。
    
    -- 相反的例子2:
    select * from B where cc in (select cc from A)
    -- 效率高,用到了B表上cc列的索引;
    
    select * from B where exists(select cc from A where cc=B.cc)
    -- 效率低,用到了A表上cc列的索引。

      not in 和 not exists 如果查询语句使用了 not in 那么内外表都进行全表扫描,没有用到索引;而 not extsts 的子查询依然能用到子表上的索引。

      因为not in实质上等于!= and != ...,因为 != 不会使用索引,故 not in 不会使用索引。

      所以无论那个表大,用 not exists 都比 not in 要快

    3、exists 与 in 都可以实现一个目的,二者都可以用来过滤数据。

    select count(1) from t1;     --160W
    select count(1) from t2;     --90W
    
    SELECT count(1)
    FROM t1 a
    WHERE EXISTS (SELECT accountid
    FROM t2 b
    WHERE a.keyid = b.keyid AND a.ideaid = b.ideaid);
    --主大子小,不适合使用exist,因为exist只会利用子表t2的复合索引keyid+ideaid,而子表内容要小与主表,主表由于无法使用索引,查询效率低下.
    
    select count(1) from t1 a where accountid in (SELECT accountid
    FROM t2 b
    WHERE a.keyid = b.keyid AND a.ideaid = b.ideaid);
    --主大子小,适合用in,因为in只会使用主表t1里面的复合主键keyid-ideaid,在主表大于子表的情况下,会很好的利用主表的索引

      后二条sql的执行结果都是一样的,说明 exists 与 in 在用法上可以达到一个目的,不同的地方是:

    (1)性能的考虑:此时就按子表大主表小用exists,子表小主表大用 in 的原则就可以。

    (2)写法的不同:exists 的where条件是"......  where exists (..... where a.id=b.id)",in的where条件是: " ...... where  id in ( select id .... where a.id=b.id)"

    4、exists 的原理

      exists 做为 where 条件时,是先对 where 前的主查询询进行查询,然后用主查询的结果一个一个的代入exists的查询进行判断,如果为真则输出当前这一条主查询的结果,否则不输出。

    -- 比如如下:
    表A
    ID NAME
    1 A1
    2 A2
    3 A3
    
    表B
    ID AID NAME
    1 1   B1
    2 2   B2
    3 2   B3
    
    表A和表B是一对多的关系 A.ID --> B.AID
    
    SELECT ID , NAME FROM A WHERE EXISTS (SELECT * FROM B WHERE A.ID = B.AID)
    -- 执行结果为
    1 A1
    2 A2 

      原因可以按照如下分析:

    SELECT ID , NAME FROM A WHERE EXISTS (SELECT * FROM B WHERE B.AID = 1)
    -->SELECT * FROM B WHERE B.AID = 1有值返回真所以有数据
    
    SELECT ID , NAME FROM A WHERE EXISTS (SELECT * FROM B WHERE B.AID = 2)
    -->SELECT * FROM B WHERE B.AID = 2有值返回真所以有数据
    
    SELECT ID , NAME FROM A WHERE EXISTS (SELECT * FROM B WHERE B.AID = 3)
    -->SELECT * FROM B WHERE B.AID = 3无值返回真所以没有数据 

      NOT EXISTS 就是反过来。

    5、in 与 = 的区别

    select name from student where name in ('zhang','wang','li','zhao');
    --
    select name from student where name='zhang' or name='li' or name='wang' or name='zhao'
    -- 的结果是相同的。

      in 的字段也可以与其它字段建复合索引

    -- 比如:T1包含下面key, accountd,groupid
    SELECT   *
      FROM   T1  a
     WHERE       a.groupid = 2001
             AND a.accountid = 1001
             AND a.key IN ('abc', 'def', 'ala');
    -- 上面的sql可以将accountid,key建成复合索引

    二、in 查询执行流程

    (一)确定给定的值是否与子查询或列表中的值相匹配。in在查询的时候,首先查询子查询的表,然后将内表和外表做一个笛卡尔积,然后按照条件进行筛选。所以相对内表比较小的时候,in的速度较快。

    SELECT * FROM user WHERE user.id 
    IN (SELECT order.user_id FROM order)

      这条语句很简单,通过子查询查到的user_id 的数据,去匹配user表中的id然后得到结果。它的执行流程是什么样子的呢?让我们一起来看一下。

      查询中涉及到的两个表,一个user和一个order表,具体表的内容如下:

    (1)user表:

    (2)order表:

     

    1、首先,在数据库内部,查询子查询,执行完毕后,得到结果。

    2、然后,将查询到的结果和原有的user表做一个笛卡尔积。(结果如下)

    3、然后,再根据我们的 user.id IN order.user_id 的条件,将结果进行筛选(既比较id列和user_id 列的值是否相等,将不相等的删除);

    4、最后,得到两条符合条件的数据。

    (二)select * from A where id in(select id from B)

      以上查询使用了in语句,in()只执行一次,它查出B表中的所有id字段并缓存起来。之后检查A表的id是否与B表中的id相等,如果相等则将A表的记录加入结果集中,直到遍历完A表的所有记录。它的查询过程类似于以下过程:

      List resultSet=[]; Array A=(select * from A); Array B=(select id from B);
      for(int i=0;i<A.length;i++) {    for(int j=0;j<B.length;j++) {       if(A[i].id==B[j].id) {          resultSet.add(A[i]);          break;       }    } } return resultSet;

      可以看出,当B表数据较大时不适合使用in(),因为它会B表数据全部遍历一次。如:A表有10000条记录,B表有1000000条记录,那么最多有可能遍历10000*1000000次,效率很差. 再如:A表有10000条记录,B表有100条记录,那么最多有可能遍历10000*100次,遍历次数大大减少,效率大大提升。

      结论:in()适合B表比A表数据小的情况

    三、exists 查询执行流程

    (一)指定一个子查询,检测行的存在。遍历循环外表,然后看外表中的记录有没有和内表的数据一样的。匹配上就将结果放入结果集中。

    SELECT user.* FROM user WHERE
    EXISTS ( SELECT order.user_id FROM order WHERE user.id = order.user_id)

      这条sql语句的执行结果和上面的in的执行结果是一样的,但是,它们的执行流程完全不一样:

    1、使用exists关键字进行查询的时候,首先,我们先查询的不是子查询的内容,而是查我们的主查询的表,也就是说,我们先执行的sql语句是:SELECT user.* FROM user

    2、然后,根据表的每一条记录,执行 exists 语句,依次去判断where后面的条件是否成立。

      如果成立则返回true不成立则返回false。如果返回的是true的话,则该行结果保留,如果返回的是false的话,则删除该行,最后将得到的结果返回。

    (二)select a.* from A a where exists(select 1 from B b where a.id=b.id)

      以上查询使用了exists语句,exists()会执行A.length次,它并不缓存exists()结果集,因为exists()结果集的内容并不重要,重要的是结果集中是否有记录,如果有则返回true,没有则返回false。它的查询过程类似于以下过程:

      List resultSet=[]; Array A=(select * from A)
      for(int i=0;i<A.length;i++) {    if(exists(A[i].id) {    //执行select 1 from B b where b.id=a.id是否有记录返回        resultSet.add(A[i]);    } } return resultSet;

      当B表比A表数据大时适合使用exists(),因为它没有那么遍历操作,只需要再执行一次查询就行。如:A表有10000条记录,B表有1000000条记录,那么exists()会执行10000次去判断A表中的id是否与B表中的id相等。如A表有10000条记录,B表有100000000条记录,那么exists()还是执行10000次,因为它只执行A.length次,可见B表数据越多,越适合exists()发挥效果。

      再如A表有10000条记录,B表有100条记录,那么exists()还是执行10000次,还不如使用in()遍历10000*100次,因为in()是在内存里遍历比较,而exists()需要查询数据库,我们都知道查询数据库所消耗的性能更高,而内存比较很快。

      结论:exists()适合B表比A表数据大的情况

      当A表数据与B表数据一样大时,in与exists效率差不多,可任选一个使用。

    四、区别及应用场景

    1、如果子查询得出的结果集记录较少,主查询中的表较大且又有索引时应该用in;反之如果外层的主查询记录较少,子查询中的表大,又有索引时使用exists。

      其实我们区分 in 和 exists 主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询,所以我们会以驱动表的快速返回为目标,那么就会考虑到索引及结果集的关系了 ,另外IN时不对NULL进行处理。

    2、in 是把外表和内表作 hash 连接,而 exists 是对外表作 loop 循环,每次 loop 循环再对内表进行查询。一直以来认为 exists 比 in 效率高的说法是不准确的。

    3、如果查询语句使用了not in 那么内外表都进行全表扫描,没有用到索引;而not extsts 的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。

    执行流程参考文章:https://www.cnblogs.com/emilyyoucan/p/7833769.html

  • 相关阅读:
    权限管理命令
    常用命令2
    常用命令1
    queue
    poj 3984
    L3-008 喊山 (30 分)
    常州大学新生寒假训练会试 I 合成反应
    dfs 的全排列
    poj 1154
    hdu 1241
  • 原文地址:https://www.cnblogs.com/goloving/p/15226232.html
Copyright © 2020-2023  润新知