实践:mysql中exists与in性能对比

背景:
本人在工作开发的时候遇到过项目中一种情况,在业务数据日益增长的情况下,某个服务日志经常会出现服务超时的情况,代码分析后发现问题出在某一条比较复杂的sql语句中使用到了not in,直接将他改为not exists之后,发现服务执行要快了很多。

测试准备:
用户身份识别记录表tbl_visitor_identification,数据量为305W;
新用户身份识别记录表tbl_visitor_first_identification,数据量为104W。

测试内容

  1. in

    select
    udc_biz_date,udc_visitor_sequence,udc_visitor_type
    from
    (select
    udc_biz_date,
    (
    case
    when udc_customer_id !=’ ’
    then udc_customer_id
    else
    udc_cookie
    end
    )as udc_visitor_sequence, --用户唯一标志序列号(udc_customer_id/udc_cookie)
    (
    case
    when udc_customer_id !=’ ’
    then ‘01’
    else
    ‘02’
    end
    )as udc_visitor_type --用户类型(01:客户;02:游客)
    from dzyh_app.TBL_VISITOR_IDENTIFICATION
    ) a
    where
    a.udc_visitor_sequence in
    (select udc_visitor_sequence from tbl_visitor_first_identification)

执行效果如下:
实践:mysql中exists与in性能对比_第1张图片
经过多次试验,执行时间大概是在35s-37s之间。

  1. exists

    select
    udc_biz_date,udc_visitor_sequence,udc_visitor_type
    from
    (select
    udc_biz_date,
    (
    case
    when udc_customer_id !=’ ’
    then udc_customer_id
    else
    udc_cookie
    end
    )as udc_visitor_sequence, --用户唯一标志序列号(udc_customer_id/udc_cookie)
    (
    case
    when udc_customer_id !=’ ’
    then ‘01’
    else
    ‘02’
    end
    )as udc_visitor_type --用户类型(01:客户;02:游客)
    from dzyh_app.TBL_VISITOR_IDENTIFICATION
    ) a
    where
    exists
    (select udc_visitor_sequence from tbl_visitor_first_identification where udc_visitor_sequence = a.udc_visitor_sequence);"

执行效果如下:
实践:mysql中exists与in性能对比_第2张图片
经过多次试验,执行时间大概是在0.9s-2s之间。

分析
首先,在项目中关键sql语句中并没有使用索引,所以这种情况是在都无索引下exists与in所做的对比,可以看出,exists要比in性能要好太多,但是不是所有情况都如此呢?
我们首先明确几个小概念,例如

 select * from a where a.id in  (select  id  from b);
 select * from a where exists  (select  id  from b where a.id=b.id);

在这两句语句中,a表我们把它称之为外表,b表我们把它称之为内表。

  1. in会把外表和内表作hash 连接,in内的子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响,特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大, 由于子查询会产生大量的临时表也没有索引,所以会消耗过多的CPU和IO资源,产生大量的慢查询;
  2. exists是对外表作loop循环,每次loop循环再对内表进行查询。
  3. 因此根据外表跟内表数据大小的差异,分为以下几种情况:
    a. 外表数据大,内表数据小的exists与in: 这种情况应当使用in,虽然会存储在临时表,但是内表数据小,因此查询次数不会那么多,而exists外表loop循环会占用较长时间,所以不适合内表数据小的情况;
    b. 外表数据小,内表数据大的exists与in: 这种情况应当使用exists,内表数据量太大导致临时表太大,占用过多IO与CPU使性能下降,而exists的loop循环在这种情况下会会占用较少资源,效率高;
    c. 外表内表数据大小相当的exists与in: 这种情况in与exists性能也相当。
    d.表中有索引的情况:exists与in均不会使索引失效,使用状况参照上边三种情况。但是not exists与not in的情况中,not exists仍旧是索引生效,但是not in会使索引失效导致全表扫描,因此在not exists与not in情况中,均推荐使用not exists。

总结

  1. in内的子查询的结果集会被存储到临时表,而exists主要是做了内外表的loop循环;
  2. exists与in均不会使索引失效,外表大内表小推荐in,外表小内表大推荐exists,大小相当两种均可用;
  3. not in会使索引失效,而not exists仍旧使索引生效,不管何种情况推荐使用not exists。
  4. 总之,区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。.
    ps:本文的内外表的大小并非两个表数据量相比的大小,我的理解是相对于临时表与loop循环中平衡值的大与小,如有其他见解,还望指点。

你可能感兴趣的:(mysql,工作笔记)