半连接物化反连接

Posted 戚焱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了半连接物化反连接相关的知识,希望对你有一定的参考价值。

半连接、物化、反连接

且将新火试新茶

mysql查询优化器有多种策略可用于评估子查询:

  • 对于IN(或=ANY)子查询,优化器具有以下选择:
    • 半连接
    • 物化
    • EXISTS
  • 对于NOT IN(或 <>ALL)子查询,优化器具有以下选择:
    • 物化
    • EXISTS

semi-join半连接

我们来看一个mysql官方给出的案例:

假设有一个名为class和roster的表,分别表示班级表和班级–学生对应关系。要列出实际有学生的班级,可以使用以下连接:

class_num:班级编号(主键),class_name:班级名称

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

但是,结果为每个学生都列出一次所选的班级。对于所提出的问题,这是不必要的信息重复。对于有学生的班级,我们只需要拿到一次班级的信息即可。

可以使用distinct来消除重复的记录。

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

但是distinct会先生成所有匹配的记录,然后再去消除重复,效率并不高。

使用子查询如何呢?

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

下面这条使用exists的语句等效于上面的in语句

SELECT class_num, class_name
    FROM class
    WHERE EXISTS
        (SELECT * FROM roster WHERE class.class_num = roster.class_num);

mysql会采用半连接的方式来优化子查询,半连接中:mysql只关心外层查询的某条记录在子查询的结果集中是否可以匹配,而不考虑这条记录在子查询的结果集中存在多少条匹配的记录。

半连接实现的几种方法:

  • 将子查询转换为联接,或者使用table pullout并将查询作为子查询表和外部表之间的内部联接运行。Table pullout将表从子查询拉出到外部查询。即将in操作转变为inner join操作,如果子查询的列本身就是唯一不重复的,那么inner join的结果也不会出现重复值。
  • Duplicate Weedout:子链接的结果集中可能会出现很多重复数据,mysql会使用临时表删除重复的记录。
  • FirstMatch:在外层查询中依次拿取一条记录,到子查询的表中进行匹配,如果匹配到就拿到第一条返回,不再继续匹配。选择一个而不是全部返回,这种“快捷方式”扫描可以消除不必要行的产生。
  • LooseScan:使用索引扫描子查询表,从每个子查询的值组中选择单个值的索引扫描子查询表。当子查询可以使用索引时(非主键索引),会拿到相同索引值中的一条记录去匹配。比如值为2的二级索引共3条,那么只需要拿到第一条去匹配外查询即可。
  • Materialize:将子查询物化到用于执行连接的索引临时表中,该临时表用于执行连接,其中索引用于删除重复项。当将临时表与外部表连接起来时,索引也可以用于以后的查找;如果没有,则扫描表。(物化)

在MySQL 8.0.16及更高版本中,任何带有EXISTS子查询的语句都应与带有IN子查询语句等效。

mysql内部使用semi-join也是有条件的,并不是带有in或exists的查询就能使用半连接:

  • 该子查询必须是和IN语句组成的布尔表达式,并且在外层查询的WHERE或者ON子句中出现。
  • 不能有LIMIT。
  • 不能包含任何聚合函数(无论是显式分组还是隐式分组)。
  • 不能包含HAVING语句
  • 必须是没有union构造的单个SELECT。
  • 可以有DISTINCT,但是会忽略,因为半连接策略会自动处理删除重复项。
  • 可以有ORDER BY,但是会忽略,因为排序无关半连接策略执行与评估。
  • 允许但忽略GROUPBY子句,除非子查询还包含一个或多个聚合函数。
  • 子查询可以是相关的也可以是不相关的
物化

物化通过生成子查询结果作为临时表(通常在内存中)来加快查询的执行速度。MySQL第一次需要子查询结果时,会将结果具体化为临时表。任何随后的需要结果的时间,MySQL都会再次引用临时表。

mysql会把临时表存储在内存中,并且为该表建立哈希索引。如果结果集非常大,临时表会被存储在磁盘中,索引类型会变为B+树索引。

重复的记录对于in来说,结果并不改变。所以把结果集假如到临时表的同时会去重,使得临时表变得更小。

物化表中的记录都建立了索引,所以使用in是非常快的。

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

优化器可能将此重写为 EXISTS相关子查询:

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

但像刚才半连接中说的,mysql会考虑最佳的执行策略,如果mysql采用物化,则不会将in重写为exists。而是使用临时表存储子查询的结果集。使得子查询只执行一次,而不是对外部查询的每一行执行一次。

这样像刚才那条不相关子查询就被优化成

select * from user inner join 临时表 on user_id = 临时表字段

物化也是有条件的:

  • 内部和外部表达式的类型必须匹配。
  • 内部表达式不能是BLOB

反链接

从MySQL 8.0.17开始,以下子查询被转换为反联接:

  • 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

反链接就是外部查询中的记录不能匹配子查询结果集

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

该查询mysql会重写为:

SELECT class_num, class_name FROM class ANTIJOIN roster ON class_num

只要外部查询中的某一条记录,能匹配上子查询中的任意一条,就说明该记录就应该被丢弃。

反链接与半连接相似,mysql也会选择它认为更好的执行策略去执行,比如物化与FirstMatch。

如有错误,请不吝赐教。

以上是关于半连接物化反连接的主要内容,如果未能解决你的问题,请参考以下文章

半连接物化反连接

半连接物化反连接

Postgres:更新与物化视图连接的表?错误:视图无法在物化视图中锁定行

Oracle优化笔记

雪花物化视图可以包含半结构化数据的展平吗?

无法使用连接和分组创建实体化视图