zoukankan      html  css  js  c++  java
  • MySQL Execution Plan--NOT EXISTS子查询优化

    在很多业务场景中,会使用NOT EXISTS语句来确保返回数据不存在于特定集合,部分场景下NOT EXISTS语句性能较差,网上甚至存在谣言"NOT EXISTS无法走索引"。

    首先需要明确的是:索引不是万能的,使用索引的执行计划并不一定就是最好的执行计划。

    以某监控平台为例,使用NOT EXISTS的SQL为:

    SELECT count(1)
    FROM t_monitor m
    WHERE NOT exists
    (
        SELECT 1
        FROM t_alarm_realtime AS a
        WHERE a.resource_id=m.resource_id
        AND a.resource_type=m.resource_type
        AND a.monitor_name=m.monitor_name
    )

    该SQL执行时间为29秒,其执行计划为:

    可以发现,上面SQL中使用到索引,但是表t_monitor上影响行数较高(578436),相当于遍历表t_monitor上索引idx_id_name_type上所有数据。

    对于NOT EXISTS语句,常用的优化手段之一就是将NOT EXIST语句转换为LEFT JOIN语句,如将上面的SQL转换为:

    SELECT count(1)
    FROM t_monitor m
    LEFT JOIN t_alarm_realtime AS a
    ON a.resource_id=m.resource_id
    AND a.resource_type=m.resource_type
    AND a.monitor_name=m.monitor_name
    WHERE a.resource_id is NULL

    PS1:将NOT EXISTS转换为LEFT JOIN时,需要确定两表在关联条件上的数据处于1:1或1:0,否则需要在JOIN前或JOIN后对数据进行去重操作(DISTINCT)。

    改写后SQL执行时间为1.2秒,性能提升约25倍,改写后执行计划为:

    粗略对比两个执行计划,会发现相似度很高,使用的索引页相同,仅仅是select_type和Extra两列存在差异

    两个执行计划差异:NOT EXISTS语句使用"DEPENDENT SUBQUERY",而LEFT JOIN使用SIMPLE方式。

    为什么两者会有如此大差距呢?可以通过MySQL提供的Profiling方式来查看两种方式的执行过程。

    使用NOT EXIST方式的执行过程:

    使用LEFT JOIN方式的执行过程:

    从执行过程来看,LEFT JOIN方式的主要消耗在Sending data一项上(1.2s),而NOT EXISTS方式主要消耗在executeing和Sending data两项上,受限于Profiling只存放100行记录缘故,从Profiling中只能看到47个” executeing和Sending data”的组合项(每个组合项约50us),通过执行计划看出,外表t_monitor的数据量为578436行,忽略统计信息不准情况下,使用NOT EXISTS方式应该会产生578436个” executeing和Sending data”的组合项,总计消耗时间=50μs*578436=28921800us=28.92s。

    PS2:在MySQL 5.5版本中,使用Profiling查看执行过程会返回所有的步骤,而MySQL 5.7版本中,将步骤数量控制在100行以内,避免展示过多重复步骤影响查看。

    问题总结:

    对于NOT EXISTS方式语句,其执行性能严重依赖于子查询的循环执行次数,即外层查询结果集的数据量:

    1、当外层查询结果集的数据量N较小时执行性能较好,如有N=10执行时间为50μs*10=500us=0.005s,再加上一些额外消耗,执行结果也能在0.01秒或10毫秒内范围,这个响应时间应该能被大部分应用程序接受。

    2、当外层程勋结果集的数据量N较大甚至上千万数据量时,NOT EXISTS的查询性能会变得非常糟糕,甚至会大量消耗服务器IO和CPU资源从而影响其他业务正常运行。

    NOT EXISTS语句相对于LEFT JOIN语句更容易书写和便于理解,任何查询方式都有其适用场景,存在即合理。

  • 相关阅读:
    hmac模块和hashlib模块
    logging模块
    sys模块
    datetime模块
    time模块
    例题:100节楼梯,0-49节,分数等于节数。50节(包括50节)以后每节10分。输入节数,得出分数。这个题如果按照讲页来做是错误的,所以再写一遍,请大家指导
    C# .ToString() 格式化
    例题:判断平年还是闰年。理解使用异常语句try catch finally 和datetime 时间类
    SQL Server第一堂课:创建数据库,创建表,以及表中最基本的增,删,改
    例题:输入学生的各项资料,然后根据学生的分数,重新排序。重新复习结构体,集合,数组,for循环,冒泡排序,水平符的使用。
  • 原文地址:https://www.cnblogs.com/gaogao67/p/10721052.html
Copyright © 2011-2022 走看看