• Mysql优化(出自官方文档)


    Mysql优化(出自官方文档) - 第六篇

    Optimizing Subqueries, Derived Tables, View References, and Common Table Expressions

    对于子查询,Mysql通常使用如下的优化方式:

    • 对于IN(or =ANY)式的子查询,优化器使用如下方式:
      • semijoin
      • 物化
      • EXISTS策略
    • 对于NOT IN(OR <>ALL)式的子查询,优化器使用如下方式:
      • 物化
      • EXISTS策略

    对于derived tables,优化器使用如下方式(对于view referencescommon table expressions也同样适用):

    • derived table合并到外部查询里
    • derived table物化为临时表。

    特别的,对于带有子查询的UPDATEDELETE语句,优化方式为:

    优化器不会使用semijoin和物化来优化这种情况,而是将其重写为多张表的UPDATEDELETE操作,同时使用join来代替子查询操作。

    1 Optimizing IN and EXISTS Subquery predicates with Semijoin Transformations

    假设有两张表,classroster,现在要查询有学生出勤课程的课程编号和课程名称,我们可以很简单的写出下面的语句:

    SELECT class.class_num, class.class_name
    FROM class INNER JOIN roster
    WHERE class.class_num = roster.class_num;
    

    假设class_numclass表的primary key,可以看出来,上面的查询结果中必然有重复列,因为多个学生可以出勤同一个课程,所以,为了去重,我们可以加上SELECT DISTINCT这样的限定。

    除此之外,还可以将上面的join语句改为子查询的方式,如下:

    SELECT class_num, class_name
    FROM class
    WHERE class_num IN (SELECT class_num FROM roster);
    

    该语句有如下特点:

    • SELECT的目标只有一张表的列
    • IN表示在第二张表中只要有第一张表相同的值就立即返回

    此时,Mysql会将上面的子查询优化为semijoinsemijoin的特点就是在join中一旦查询到匹配行,就立即只返回一张表的数据,后续重复的值将没有必要继续扫描。

    同时,在Mysql8.0.17之后,下面的语句也会被转换为antijoin(和semijoin相反,当第一张表在第二张表中没有匹配行时,立即返回第一张表的列。)

    • NOT IN (SELECT ... FROM ...)
    • NOT EXISTS (SELECT ... FROM ...).
    • IN (SELECT ... FROM ...) IS NOT TRUE
    • EXISTS (SELECT ... FROM ...) IS NOT TRUE.
    • IN (SELECT ... FROM ...) IS FALSE
    • EXISTS (SELECT ... FROM ...) IS FALSE.

    对于semijoin,Mysql主要的处理方式如下:

    • Duplicate Weedout
    • FirstMatch
    • LooseScan
    • Materialize

    这四种的实现方式网上均有介绍,这里就不赘述了。

    2 Optimizing Subqueries with Materialization

    Mysql经常使用物化的方式来优化subquery,通常的方式是创建一个临时表(一般来讲是全内存临时表,只有当临时表变得比较大的时候,才会进行下盘处理),并且优化器会使用hash index的方式对临时表创建索引来加快查询,index的值是唯一的,所以能够避免重复的值。

    对于下面的语句,如果不适用物化的话:

    SELECT * FROM t1
    WHERE t1.a IN (SELECT t2.b FROM t2 WHERE where_condition);
    

    优化器会将该语句重写为:

    SELECT * FROM t1
    WHERE EXISTS (SELECT t2.b FROM t2 WHERE where_condition AND t1.a=t2.b);
    

    这种有所关联的子查询语句(关联指子查询语句中不仅查询t2内表的数据,还会和t1外表有关), 这种类型的子查询的执行过程为:外表每执行一次,子查询就要执行一次,所以效率很低。

    为了让Mysql使用物化来运行子查询,查询语句必须符合如下形式:

    • 外查询中oe_i和内查询ie_i不能为nullN1或者更大的值(??为什么?不是很理解?)

      (oe_1, oe_2, ..., oe_N) [NOT] IN (SELECT ie_1, i_2, ..., ie_N ...)
      
    • 外查询和内查询均只有一个表达式,表达式的值可以为null

      oe [NOT] IN (SELECT ie ...)
      
    • 谓词必须为in或者not in,或者和FALSE具有相同语义的表达式。

    举例如下,下面的语句将会使用物化技术:

    SELECT * FROM t1
    WHERE t1.a IN (SELECT t2.b FROM t2 WHERE where_condition);
    

    下面的语句将无法使用物化技术,因为t2.b可能为null

    SELECT * FROM t1
    WHERE (t1.a,t1.b) NOT IN (SELECT t2.a,t2.b FROM t2
                              WHERE where_condition);
    

    需要注意的是,对于列的类型信息,必须满足如下条件,才能使用物化技术:

    • 内查询和外查询的列必须匹配,比如如果一个integer另外一个是decimal,优化器将无法使用物化技术。
    • 内查询的表达式类型不能为BLOB,根据第一条的限制,外查询也同样不能为BLOB

    在EXPLAIN的输出里面,如果使用了物化技术,那么输出如下:

    • select_type将由DEPENDENT SUBQUERY 变为SUBQUERY, DEPENDENT的意思是外查询每执行一次,内查询就要执行一次,使用物化技术的话,内查询只需要执行一次。
    • EXPLAIN的输出里面,SHOW WARNINGS会包含materializematerialized-subquery

    3 Optimizing Subqueries with the EXISTS Strategy

    对于如下的语句,如果不采取章节2中的优化方式,那么通常的执行方式是:外查询执行一次,内查询在执行一次。

    outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)
    

    对于这种情况,由于只需要特定列,所以Mysql通常会使用条件下推的方式进行优化,优化后的结果为:

    EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr)
    

    这样子,子查询的条件将更加严格,可以大大降低子查询需要行数。

    同理,如果选取的是多列,那么也可以采用同样的优化方式:

    (oe_1, ..., oe_N) IN
      (SELECT ie_1, ..., ie_N FROM ... WHERE subquery_where)
    

    上面这条语句将会优化为:

    EXISTS (SELECT 1 FROM ... WHERE subquery_where
                              AND oe_1 = ie_1
                              AND ...
                              AND oe_N = ie_N)
    

    这种条件下推的方式有其局限性,如下:

    • outer_exprinner_expr均不能为NULL

    • 如果OR或者AND语句在WHERE语句中,Mysql假设用户并不关心返回的值是NULL还是FALSE,因此,对于下面的语句:

      ... WHERE outer_expr IN (subquery)
      

      无论子查询返回NULL还是返回FALSEWHERE都不会接受。

    如果说,上面两条限制都不符合,那么此时的优化方式将会变的很复杂,主要分为以下两种情况:

    • 如果outer_expr不会有NULL产生,此时,outer_expr IN (SELECT ...)的结果分为以下两种情况:

      • 如果SELECTinner_expr is NULL的条件下返回了任意行,那么结果为NULL
      • 如果SELECT只返回了非NULL行或者说没有返回任何一行,那么结果为FALSE

      对于这种情况,当查找outer_expr = inner_expr时,如果没有找到,还需要查找inner_expr is NULL这样的列,因此,这种语句会被转换为下面的形式:

      EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
              (outer_expr=inner_expr OR inner_expr IS NULL))
      

      EXPLAIN里面,这样的语句type列为: ref_or_null

    • 如果outer_expr有可能产生NULL列,那么情况将会变得比较复杂,对于NULL IN (SELECT inner_expr ...)这样的语句,结果分为两种情况:

      • 如果SELECT返回了任意行,那么结果为NULL
      • 如果SELECT没有返回行,那么结果为FALSE

      所以,优化器为了加快速度,需要分两种情况来处理:

      • 如果outer_expr的结果为NULL,就需要判断子查询是否返回任意行,此时无法使用条件下推的优化,这个时候的性能是最差的,外查询没查询一次,就需要执行一次子查询。

      • 如果outer_expr的结果不为NULL,那么就可以使用上面提到的条件下推这种优化方式,语句将会被重写为:

        EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr)
        

        综上,为了包含上面两种情况,Mysql使用一种叫做"trigger"的函数(不同于数据库里面创建的trigger),在Mysql里面体现为Item_func_trig_cond类,因此,上面的两种情况都会统一被转换为:

        EXISTS (SELECT 1 FROM ... WHERE subquery_where
                                  AND trigcond(outer_expr=inner_expr))
        

        同理,如果有多列,如下面的语句:

        (oe_1, ..., oe_N) IN (SELECT ie_1, ..., ie_N FROM ... WHERE subquery_where)
        

        将会被转换为:

        EXISTS (SELECT 1 FROM ... WHERE subquery_where
                                  AND trigcond(oe_1=ie_1)
                                  AND ...
                                  AND trigcond(oe_N=ie_N)
               )
        

        对于trigcond(x),Mysql的处理方式如下:

        • 如果外查询的结果不是NULL,那么trigcond的结果即为x的结果
        • 如果外查询的结果为NULL,那么trigcond返回TRUE(这里不是很理解,需要详细理解下)

        Note

        这里的trigger不同于平时使用sql创建的trigger``,CREATE TRIGGER

    帮助优化器进行优化的一些技巧:

    • 如果某一列永远不会产生NULL列,那么将其声明为NOT NULL,这样子可以帮助优化器进行更进一步的优化。

    • 如果不需要区分NULLFALSE,对于下面的语句:

      outer_expr IN (SELECT inner_expr FROM ...)
      

      为了避免Mysql采用最糟糕的方式进行执行,可以将该语句修改为:

      (outer_expr IS NOT NULL) AND (outer_expr IN (SELECT inner_expr FROM ...))
      

      这样子Mysql可以使用短路判断尽快返回结果,可以大量的减少AND后面语句的执行次数。

      另外,也可以写成下面这样子:

      EXISTS (SELECT inner_expr FROM ...
              WHERE inner_expr=outer_expr)
      

    4 Optimizing Derived Tables, View References, and Common Table Expressions with Merging or Materialization

    在优化derived table的时候,通常采用两种策略(同样适用于view referencescommon table expressions):

    • derived table合并到外层的查询里面
    • 物化derived table到一个临时表里面

    举例如下:

    SELECT * FROM (SELECT * FROM t1) AS derived_t1;
    

    会被优化为:

    SELECT * FROM t1;
    

    下面的语句:

    SELECT *
      FROM t1 JOIN (SELECT t2.f1 FROM t2) AS derived_t2 ON t1.f2=derived_t2.f1
      WHERE t1.f1 > 0;
    

    会被优化为:

    SELECT t1.*, t2.f1
      FROM t1 JOIN t2 ON t1.f2=t2.f1
      WHERE t1.f1 > 0;
    

    可以明显的看到,如果采取非物化的方式,执行效率将会大大提升。

    优化器会将derived table里面的ORDER BY优化到外层查询语句中,但是,必须满足如下条件:

    • 外层查询没有使用group by或者聚合函数
    • 外层查询没有使用DISTINCT, HAVING, or ORDER BY
    • 外层查询只有在FROM中才使用到了derived table

    如果优化器没办法使用MERGE,意味着只能使用物化为临时表的方式来执行,此时,为了加快效率,将采用如下的优化方式:

    • 优化器只有在需要derived table的时候才会进行物化操作,这样子有时候就可以避免进行物化操作。比如说:一个查询中子查询需要进行物化操作,条件里面有外表和dervied table的对比,此时先执行外查询,如果外查询返回了空行,这个时候,dervied table就没必要再继续执行了,可以减少没有必要的物化操作。
    • 在执行期间,优化器会根据需要给dervied table添加对应的索引,这样子可以提高dervied table的访问效率。
  • 相关阅读:
    c#接口和抽象类的区别(转)
    Dephi阿拉伯数字转换成英文和中文大写
    Code Rush Express Template 制作
    SQL中对学习成绩自动排名次
    Resharper上手指南
    如何實現域控制中部分用戶可以寫Programme files目錄的權限?
    阿拉伯数字转换英文数字表示算法解析及其实现
    BOM 算法
    OGRE1.7.1.1vs2008安装
    近一个月工作小总结
  • 原文地址:https://www.cnblogs.com/seancheer/p/11288623.html
Copyright © 2020-2023  润新知