且将新火试新茶
MySQL查询优化器有多种策略可用于评估子查询:
IN
(或=ANY
)子查询,优化器具有以下选择:
EXISTS
NOT IN
(或 <>ALL
)子查询,优化器具有以下选择:
EXISTS
我们来看一个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只关心外层查询的某条记录在子查询的结果集中是否可以匹配,而不考虑这条记录在子查询的结果集中存在多少条匹配的记录。
半连接实现的几种方法:
在MySQL 8.0.16及更高版本中,任何带有EXISTS
子查询的语句都应与带有IN
子查询语句等效。
mysql内部使用semi-join也是有条件的,并不是带有in或exists的查询就能使用半连接:
IN
语句组成的布尔表达式,并且在外层查询的WHERE
或者ON
子句中出现。物化通过生成子查询结果作为临时表(通常在内存中)来加快查询的执行速度。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 = 临时表字段
物化也是有条件的:
从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。
如有错误,请不吝赐教。