semi-join子查询优化 -- semi-join Materialization策略
semi-join Materialization 是用于semi-join的一种特殊的子查询物化技术。通常包含两种策略:
1.Materialization/lookup
2.Materialization/scan
考虑一个查询欧洲有大城市的国家:
select * from Country
where Country.code IN (select City.Country
from City
where City.Population > 7*1000*1000)
and Country.continent='Europe'
子查询是非相关子查询。也即是我们可以独立运行内查询。semi-materialization的思想是使用city.country中可能的值填充一个临时表,然后和欧洲的国家进行关联。

这个join可以从两个方向进行:
1.从物化表到国家表
2.从国家表到物化表
第一个方向涉及一个全表扫描(在物化表上的全表扫描),因此被称为"Materialization-scan"
如果从第二个方向进行,最廉价的方式是使用主键从物化表中lookup出匹配的记录。这种方式被称为"Materialization-lookup"。
Materialization-scan
如果我们寻找人口超过700万的城市,优化器将使用materialize-scan,EXPLAIN输出结果也会显示这一点:
MariaDB [world]> explain select * from Country where Country.code IN (select City.Country from City where City.Population > 7*1000*1000);
+----+--------------+-------------+--------+--------------------+------------+---------+--------------------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+-------------+--------+--------------------+------------+---------+--------------------+------+-----------------------+
| 1 | PRIMARY | <subquery2> | ALL | distinct_key | NULL | NULL | NULL | 15 | |
| 1 | PRIMARY | Country | eq_ref | PRIMARY | PRIMARY | 3 | world.City.Country | 1 | |
| 2 | MATERIALIZED | City | range | Population,Country | Population | 4 | NULL | 15 | Using index condition |
+----+--------------+-------------+--------+--------------------+------------+---------+--------------------+------+-----------------------+
3 rows in set (0.01 sec)
从上可以看到:
1.仍然有两个select(id=1和id=2)
2.第二个select(id=2)的select_type是MATERIALIZED。这表示会执行并将结果存储在一个在所有列上带有一个唯一性索引的临时表。这个唯一性索引可以避免有重复的记录
3.第一个select中接收到一个名为subquery2的表,这是从第二个select(id=2)获取的物化的表
优化器选择在物化的表上执行全表扫描。这就是Materialization-Scan策略的示例。
至于执行成本,我们将从表City读取15行,将15行写入物化表,然后读取它们(优化器假设不会有任何重复),然后对表Country执行15次eq_ref访问。总共,我们将进行45次读取和15次写入。
相比之下,如果你在MySQL中运行EXPLAIN,你会得到如下结果:
MySQL [world]> explain select * from Country where Country.code IN (select City.Country from City where City.Population > 7*1000*1000);
+----+--------------------+---------+-------+--------------------+------------+---------+------+------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+---------+-------+--------------------+------------+---------+------+------+------------------------------------+
| 1 | PRIMARY | Country | ALL | NULL | NULL | NULL | NULL | 239 | Using where |
| 2 | DEPENDENT SUBQUERY | City | range | Population,Country | Population | 4 | NULL | 15 | Using index condition; Using where |
+----+--------------------+---------+-------+--------------------+------------+---------+------+------+------------------------------------+
读的记录是(239 + 239*15) = 3824。
Materialization-Lookup
让我们稍微修改一下查询,看看哪些国家的城市人口超过1百万(而不是7百万):
MariaDB [world]> explain select * from Country where Country.code IN (select City.Country from City where City.Population > 1*1000*1000) ;
+----+--------------+-------------+--------+--------------------+--------------+---------+------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+-------------+--------+--------------------+--------------+---------+------+------+-----------------------+
| 1 | PRIMARY | Country | ALL | PRIMARY | NULL | NULL | NULL | 239 | |
| 1 | PRIMARY | <subquery2> | eq_ref | distinct_key | distinct_key | 3 | func | 1 | |
| 2 | MATERIALIZED | City | range | Population,Country | Population | 4 | NULL | 238 | Using index condition |
+----+--------------+-------------+--------+--------------------+--------------+---------+------+------+-----------------------+
3 rows in set (0.00 sec)
explain的输出结果和Materialization-scan类似,除了:
1.subquery2表是通过eq_ref访问的
2.access使用了索引distinct_key
这意味着优化器计划对物化表执行索引查找。换句话说,我们将使用Materialization-lookup策略。
在MySQL中(或者使用optimizer_switch='semi-join=off,materialization=off'),会得到这样的执行计划:
MySQL [world]> explain select * from Country where Country.code IN (select City.Country from City where City.Population > 1*1000*1000) ;
+----+--------------------+---------+----------------+--------------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+---------+----------------+--------------------+---------+---------+------+------+-------------+
| 1 | PRIMARY | Country | ALL | NULL | NULL | NULL | NULL | 239 | Using where |
| 2 | DEPENDENT SUBQUERY | City | index_subquery | Population,Country | Country | 3 | func | 18 | Using where |
+----+--------------------+---------+----------------+--------------------+---------+---------+------+------+-------------+
可以看出,这两个执行计划都将对国家表进行全面扫描。对于第二步,MariaDB将填充物化表(238行从表City读取并写入临时表),然后对表Country中的每个记录执行惟一的键查找,结果是238个惟一的键查找。总的来说,第二步将花费(239+238)= 477读取和238 temp.table的写入。
MySQL的第二步计划是使用City上的索引读取18行。它为表国家接收的每个记录的国家。计算出来的成本为(18*239)= 4302读取。如果有更少的子查询调用,这个计划将比物化的计划更好。顺便说一下,MariaDB也可以选择使用这样的查询计划(请参阅FirstMatch策略),但是它没有选择。
带有group by的子查询
当子查询带有分组的时候,MariaDB可以使用semi-join物化策略(这种场景下,其他semi-join策略不适用)
这允许高效地执行搜索某个组中最佳/最后一个元素的查询。
举个例子,我们来看看每个大陆上人口最多的城市:
explain
select * from City
where City.Population in (select max(City.Population) from City, Country
where City.Country=Country.Code
group by Continent)
+------+--------------+-------------+------+---------------+------------+---------+----------------------------------+------+-----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------+-------------+------+---------------+------------+---------+----------------------------------+------+-----------------+
| 1 | PRIMARY | <subquery2> | ALL | distinct_key | NULL | NULL | NULL | 239 | |
| 1 | PRIMARY | City | ref | Population | Population | 4 | <subquery2>.max(City.Population) | 1 | |
| 2 | MATERIALIZED | Country | ALL | PRIMARY | NULL | NULL | NULL | 239 | Using temporary |
| 2 | MATERIALIZED | City | ref | Country | Country | 3 | world.Country.Code | 18 | |
+------+--------------+-------------+------+---------------+------------+---------+----------------------------------+------+-----------------+
4 rows in set (0.00 sec)
城市是:
+------+-------------------+---------+------------+
| ID | Name | Country | Population |
+------+-------------------+---------+------------+
| 1024 | Mumbai (Bombay) | IND | 10500000 |
| 3580 | Moscow | RUS | 8389200 |
| 2454 | Macao | MAC | 437500 |
| 608 | Cairo | EGY | 6789479 |
| 2515 | Ciudad de México | MEX | 8591309 |
| 206 | São Paulo | BRA | 9968485 |
| 130 | Sydney | AUS | 3276207 |
+------+-------------------+---------+------------+
Semi-join materialization
1.可以用于非相关的in子查询。子查询可以含有分组、和/或聚合函数
2.在explain输出中,子查询会有type=Materialized;父表子查询中有table=<subqueryN>
3.开启需要将变量optimizer_switch中的materialization=on、semijoin=on
4.Non-semijoin materialization与materialization=on|off标记共享
https://mariadb.com/kb/en/library/semi-join-materialization-strategy/
semi-join子查询优化 -- semi-join Materialization策略的更多相关文章
- 转载:left join和left semi join的联系和区别
1.联系 他们都是 hive join 方式的一种,join on 属于 common join(shuffle join/reduce join),而 left semi join 则属于 map ...
- hive中left join、left outer join和left semi join的区别
先说结论,再举例子. hive中,left join与left outer join等价. left semi join与left outer join的区别:left semi join相当 ...
- join 和子查询优化
一次在家查看数据的时候,列表展示特别慢,就查看了一下,把sql语句拿出来运行居然要4,5秒,当时就感觉有问题,语句用的join链接2个表,感觉没啥错误,为啥会这么慢,然后改用了子查询链接,发现快了许多 ...
- semi-join子查询优化 -- FirstMatch策略
FirstMatch执行semi-join子查询的一种策略. 类似于MySQL 5.x中如何执行in.exists子查询. 让我们以搜索拥有大城市的国家为例: select * from Countr ...
- semi-join子查询优化 -- Duplicate Weedout策略
duplicate weedout是执行semi-join子查询的一种策略. 将semi-join作为一个常规的inner join.然后使用一个临时表,将重复的记录排除. 假设,你有一个查询,你在寻 ...
- SQL查询优化 LEFT JOIN和INNER JOIN
作者:VerySky 推荐:陈敬(Cathy) SQL查询优化 LEFT JOIN和INNER JOIN 1,连接了八个数据库表,而且全部使用LEFT JOIN,如下所示: Resource_Reso ...
- 数据库子查询和join的比较
子查询进行SELECT语句嵌套查询,可以一次完成很多逻辑上需要多个步骤才能完成的SQL操作.子查询虽然很灵活,但是执行效率并不高. select goods_id,goods_name from go ...
- 为什么MySQL不推荐使用子查询和join
前言: 1.对于mysql,不推荐使用子查询和join是因为本身join的效率就是硬伤,一旦数据量很大效率就很难保证,强烈推荐分别根据索引单表取数据,然后在程序里面做join,merge数据. 2.子 ...
- java 多线程 Thread.join子线程结束父线程再运行;join(long):等待超时毫秒数
Join的使用 目的:当子线程运行结束后,父线程才能再继续运行 /** * @ClassName ThreadJoinExample * @projectName: object1 * @author ...
- semi-join子查询优化 -- LooseScan策略
LooseScan执行semi-join子查询的一种策略. 我们将通过示例来演示这种松散(LooseScan)策略.假设,我们正在查找拥有卫星的国家.我们可以通过以下查询获得它们(为了简单起见,我们忽 ...
随机推荐
- MySQL修炼之路五
1. 存储引擎和锁 1. 存储引擎(处理表的处理器) 1. 基本操作 1. 查看所有存储引擎 mysql>show engines; 2. 查看已有表的存储引擎 mysql>show cr ...
- jQuery知识梳理20190817
目录 jQuery知识梳理20190817 1. jQuery的特征 2. jQuery的两把利器 2.1 jQuery核心函数 2.2 jQuery核心对象 3. jQuery核心函数详解 4. j ...
- 使用Gerrit发送测试邮件
使用Gerrit发送测试邮件 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.安装HTTP服务 1>.安装HTTP服务 [root@gerrit.yinzhengjie.o ...
- Python模块(导入,内置,自定义,开源)
目录: 模块介绍 自定义模块 内置模块 开源模块 一.模块 1.模块简介 模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py.模块可以被别的程序引入,以使用该模块中的函数等功能.这也是使用p ...
- spring cloud (三) 路由 zuul
1 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="h ...
- X2E车载数据记录仪
随着智能驾驶及网联技术深入应用,汽车中传输的数据量与日俱增,包括多种总线数据.视频数据.雷达数据.定位数据等等.据悉,高级别智能驾驶汽车中每秒传输的总线数据就达到G比特级别.而从产品开 ...
- Lovers(HDU6562+线段树+2018年吉林站)
题目链接 传送门 题意 初始时有\(n\)个空串,然后进行\(q\)次操作,操作分为以下两种: wrap l r x:把\(l,r\)中的每个字符串的首尾都加入\(x\),如\(s_i=121,x=3 ...
- 《exception》第九次团队作业:Beta冲刺与验收准备(第三天)
一.项目基本介绍 项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 Exception 作业学习目标 1.掌握软件黑盒测试技术:2.学会编制软件项目 ...
- 《BUG创造队》第三次作业:团队项目原型设计与开发
项目 内容 这个作业属于哪个课程 2016级软件工程 这个作业的要求在哪里 实验六 团队作业3:团队项目原型设计与开发 团队名称 BUG创造队 作业学习目标 ①掌握软件原型开发技术:②学会使用软件原型 ...
- 【CLAA系列】CLAA协议学习(CS方向)
工作上用Lora,需要开发相关模块,分享一下学习的内容: Lora: 博主是做IT的,对Lora不了解.简单理解为LPWAN(Low Power Wide Area Network)中一种技术,目前主 ...