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的访问效率。

Mysql优化(出自官方文档) - 第六篇的更多相关文章

  1. Mysql优化(出自官方文档) - 第九篇(优化数据库结构篇)

    目录 Mysql优化(出自官方文档) - 第九篇(优化数据库结构篇) 1 Optimizing Data Size 2 Optimizing MySQL Data Types 3 Optimizing ...

  2. Mysql优化(出自官方文档) - 第二篇

    Mysql优化(出自官方文档) - 第二篇 目录 Mysql优化(出自官方文档) - 第二篇 1 关于Nested Loop Join的相关知识 1.1 相关概念和算法 1.2 一些优化 1 关于Ne ...

  3. Mysql优化(出自官方文档) - 第一篇(SQL优化系列)

    Mysql优化(出自官方文档) - 第一篇 目录 Mysql优化(出自官方文档) - 第一篇 1 WHERE Clause Optimization 2 Range Optimization Skip ...

  4. Mysql优化(出自官方文档) - 第三篇

    目录 Mysql优化(出自官方文档) - 第三篇 1 Multi-Range Read Optimization(MRR) 2 Block Nested-Loop(BNL) and Batched K ...

  5. Mysql优化(出自官方文档) - 第五篇

    目录 Mysql优化(出自官方文档) - 第五篇 1 GROUP BY Optimization 2 DISTINCT Optimization 3 LIMIT Query Optimization ...

  6. Mysql优化(出自官方文档) - 第八篇(索引优化系列)

    目录 Mysql优化(出自官方文档) - 第八篇(索引优化系列) Optimization and Indexes 1 Foreign Key Optimization 2 Column Indexe ...

  7. Mysql优化(出自官方文档) - 第十二篇(优化锁操作篇)

    Mysql优化(出自官方文档) - 第十二篇(优化锁操作篇) 目录 Mysql优化(出自官方文档) - 第十二篇(优化锁操作篇) 1 Internal Locking Methods Row-Leve ...

  8. Mysql优化(出自官方文档) - 第十篇(优化InnoDB表篇)

    Mysql优化(出自官方文档) - 第十篇(优化InnoDB表篇) 目录 Mysql优化(出自官方文档) - 第十篇(优化InnoDB表篇) 1 Optimizing Storage Layout f ...

  9. Mysql优化(出自官方文档) - 第七篇

    Mysql优化(出自官方文档) - 第七篇 目录 Mysql优化(出自官方文档) - 第七篇 Optimizing Data Change Statements 1 Optimizing INSERT ...

随机推荐

  1. 面试侃集合 | ArrayBlockingQueue篇

    面试官:平常在工作中你都用过什么什么集合? Hydra:用过 ArrayList.HashMap,呃-没有了 面试官:好的,回家等通知吧- 不知道大家在面试中是否也有过这样的经历,工作中仅仅用过的那么 ...

  2. QFNU 10-16 training

    7-9.小字辈 思路:建立一个类,并且类中存有其父节点,其地位,其儿子节点(因为儿子节点有很多,所以要用vector进行存储),通过-1这个祖先节点进行查找.首先找到-1这个祖先节点,并且读入其他位置 ...

  3. Dom树,什么是dom树?

    相信很多初学前端的小伙伴,学了html,css,js之后,欣喜之余还有一丝小傲娇,没有想到那些大佬们口中又 提到了DOM树.你两眼一抹黑,年轻人总是要接受社会的爱(du)护(da). DOM 是 Do ...

  4. Linux 面试总结

    1. 统计指定目录的文件个数: find / -type f | wc –l 2.Linux 下常用目录 /boot:这个目录是用来存放与系统启动相关的文件/root:root用户的家目录/bin:存 ...

  5. 真正的原生JS数据双向绑定(实时同步)

    真正的原生JS数据双向绑定(实时同步) 接触过vue之后我感觉数据双向绑定实在是太好用了,然后就想着到底是什么原理,今天在简书上看到了一位老师的文章 js实现数据双向绑定 然后写出了我自己的代码 wi ...

  6. [c++] 模板、迭代器、泛型

    模板 函数模板:重载的进一步抽象,只需定义一个函数体即可用于所有类型 在C++中,数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当发生函数调用时,编译器可以根据传入的实参自动推 ...

  7. 联想ThinkServer服务器安装CentOS7 Redhat7系统 驱动R110i RAID卡

    1.下载对应版本的驱动(因为联想没有CentOS的驱动用redhat的驱动就可以). 2.进入BIOS里,在高级设置里找到SATA设置,把SATA模式改成RAID(重启后配置raid),sSATA模式 ...

  8. echo "This is line $LINENO"返回行号

    echo "This is line $LINENO"返回行号 LINENO 变量LINENO返回它在脚本里面的行号. #!/bin/bash echo "This is ...

  9. Deepin深度应用商店和系统更新不正常的解决方法

    Deepin深度应用商店和系统更新不正常的解决方法 2020-02-04 10:25:09作者:i8520稿源:深度站 如果你的Deepin深度应用商店和系统更新不正常,可采用以下方法来解决问题. 解 ...

  10. kvm虚拟机迁移(6)

    一.迁移简介 迁移:      系统的迁移是指把源主机上的操作系统和应用程序移动到目的主机,并且能够在目的主机上正常运行. 在没有虚拟机的时代,物理机之间的迁移依靠的是系统备份和恢复技术.在源主机上实 ...