半连接物化反连接
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。
如有错误,请不吝赐教。
以上是关于半连接物化反连接的主要内容,如果未能解决你的问题,请参考以下文章