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. Rust trait

    Rust trait Rust中的trait类似于Java中的接口,定义了一组可以被类型选择实现的"契约"或共享行为,. 特征定义: trait Playable{ fn play ...

  2. IT菜鸟之网站搭建(emlog)

    由多个网页组成的一种集合,叫做网站 网站分为:静态网站.动态网站  静态网站:不会因为时间.地点.用户角色等因素发生内容改变的网站 动态网站:会因为时间.地点.用户角色等因素发生内容改变的网站 注意: ...

  3. Python3统计gitlab上的代码量

    import threading import gitlab import xlwt #获取所有的user def getAllUsers(): usersli = [] client = gitla ...

  4. 大对象数据LOB的应用(Day_10)

    当你有永不放弃的精神,全力以赴的态度,你会惊叹自己也能创造奇迹! LOB数据类型概述 由于于无结构的数据往往都是大型的,存储量非常大,而LOB(large object)类型主要用来支持无结构的大型数 ...

  5. CSS(2)盒子模型、定位浮动

    盒子模型 盒子模型:一个盒子中主要的属性就5个.width与height.padding.border.margin.盒子模型标准有两种为标准盒模型和IE盒模型.学习上以标准盒子模型为主 width和 ...

  6. 『动善时』JMeter基础 — 33、JMeter察看结果树的显示模式详解

    目录 1.CSS Selector Tester视图 2.HTML查看器 (1)HTML视图 (2)HTML(download resources)视图 (3)HTML Source Formatte ...

  7. 详细教程丨如何利用Rancher和Kong实现服务网格?

    服务网格(Service mesh)是当前新兴的架构模式,越来越受到人们的青睐.与Kubernetes一起,服务网格可以形成一个强大的平台,它可以解决在微服务集群或服务基础设施上发现的高度分布式环境中 ...

  8. Jmeter 录制 https协议是出现“您访问的不是安全链接”提示时

    解决方法参考: https://blog.csdn.net/test_leader/article/details/112274549

  9. 面试侃集合 | SynchronousQueue非公平模式篇

    面试官:好了,你也休息了十分钟了,咱们接着往下聊聊SynchronousQueue的非公平模式吧. Hydra:好的,有了前面公平模式的基础,非公平模式理解起来就非常简单了.公平模式下,Synchro ...

  10. 实时实例分割的Deep Snake:CVPR2020论文点评

    实时实例分割的Deep Snake:CVPR2020论文点评 Deep Snake for Real-Time Instance Segmentation 论文链接:https://arxiv.org ...