为什么 EXISTS(NOT EXIST) 与 JOIN(LEFT JOIN) 的性能会比 IN(NOT IN) 好
前言
网络上有大量的资料提及将 IN 改成 JOIN 或者 exist,然后修改完成之后确实变快了,可是为什么会变快呢?IN、EXIST、JOIN 在 MySQL 中的实现逻辑如何理解呢?本文也是比较粗浅的做一些介绍,知道了 MySQL 的大概执行逻辑,也方便理解。本文绝大多数内容来自:高性能MySQL第三版(O'Reilly.High.Performance.MySQL.3rd.Edition.M),还有一部分来自于网络,还有的来自于自己的理解,以下的内容有引用的都会做标准,如有雷同,纯属巧合。
IN 改为 JOIN/EXIST
例如有如下的 IN 查询:
SELECT
*
FROM
tbl1
WHERE
col3 IN ( SELECT col3 FROM tbl2 )
如果子查询 select id from t2
数据量比较大的情况下,则会很慢,从网络找找答案,就知道往往是建议修改为:
SELECT
*
FROM
tbl1
WHERE
EXISTS ( SELECT 1 FROM tbl2 WHERE tbl1.col3 = tbl2.col3 )
或者改成 INNER JOIN 形式:
SELECT
*
FROM
tbl1
INNER JOIN tbl2 ON tbl1.col3 = tbl2.col3
确实这两种优化是可行的。不过总体来说更推荐 INNER JOIN,下面章节也会提及。
MySQL JOIN 语法的执行逻辑
一下内容摘抄自 高性能MySQL第三版(O'Reilly.High.Performance.MySQL.3rd.Edition.M),文章目录:Query Performance Optimization-->Query Execution Basics-->The Query optimizer Process-->MySQL's join execution strategy
INNER JOIN
简单的 JOIN 例子:
SELECT
tbl1.col1,
tbl2.col2
FROM
tbl1 INNER JOIN tbl2 USING ( col3 )
WHERE
tbl1.col1 IN ( 5, 6 );
MySQL 执行的伪代码:
// WHERE tbl1.col1 IN ( 5, 6 ) 筛选出 tb11 符合条件的记录
outer_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = outer_iter.next
while outer_row
// 用 tb11 的 col3 去 tbl2 表中查询,有索引将会非常快
inner_iter = iterator over tbl2 where col3 = outer_row.col3
inner_row = inner_iter.next
// 可能会命中多条数据
while inner_row
output [ outer_row.col1, inner_row.col2 ]
inner_row = inner_iter.next
end
outer_row = outer_iter.next
end
实际上就是两个循环啦,从上面的代码可以大致了解到,为什么等连接加了索引会很快,主要是因为加了索引,这条语句将走索引:inner_iter = iterator over tbl2 where col3 = outer_row.col3
LEFT JOIN
简单的例子:
SELECT
tbl1.col1,
tbl2.col2
FROM
tbl1
LEFT OUTER JOIN tbl2 USING ( col3 )
WHERE
tbl1.col1 IN ( 5, 6 );
MySQL 执行的伪代码:
// WHERE tbl1.col1 IN ( 5, 6 ) 筛选出 tb11 符合条件的记录
outer_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = outer_iter.next
while outer_row
// 用 tb11 的 col3 去 tbl2 表中查询,有索引将会非常快
inner_iter = iterator over tbl2 where col3 = outer_row.col3
inner_row = inner_iter.next
if inner_row
// 可能会命中多条数据
while inner_row
output [ outer_row.col1, inner_row.col2 ]
inner_row = inner_iter.next
end
else
// 没有命中的则返回 NULL
output [ outer_row.col1, NULL ]
end
outer_row = outer_iter.next
end
和 INNER JOIN 差不多。
MySQL Exist 语法执行逻辑
没能够找到伪代码,个人觉得应该执行逻辑和JOIN是相似的。从 高性能MySQL第三版(O'Reilly.High.Performance.MySQL.3rd.Edition.M) 找到了 Exist 与 INNER JOIN 的使用场景,文章路径:Chapter 6. Query Performance Optimization-->Limitations of the MySQL Query Optimizer-->Correlated Subqueries-->When a correlated subquery is good。
例如下面的 JOIN 语句:
SELECT DISTINCT
film.film_id
FROM
sakila.film INNER JOIN sakila.film_actor USING ( film_id );
需要对数据去重,这时候使用 EXISTS 会更合适,因为它的含义是 有一个匹配,所以平时使用的时候也得要小心,使用不当数据就被直接丢失了。改成如下的 EXISTS 语句,执行效率会更高:
SELECT
film_id
FROM
sakila.film
WHERE
EXISTS ( SELECT * FROM sakila.film_actor WHERE film.film_id = film_actor.film_id );
所以大多数时候可以使用 INNER JOIN,特别的场景使用 EXISTS。
MySQL IN 语法的执行逻辑
从官网与知名书籍中找到了如下的信息。从官网文档:C.4 Restrictions on Subqueries,有如下的文字:
The reason for supporting row comparisons for
IN
but not for the others is thatIN
is implemented by rewriting it as a sequence of=
comparisons andAND
operations. This approach cannot be used forALL
,ANY
, orSOME
.
以及高性能MySQL第三版(O'Reilly.High.Performance.MySQL.3rd.Edition.M),文章目录:Chapter 6. Query Performance Optimization-->The Query Optimization Process-->The Query optimizer-->IN() list comparisons 下有如下描述:
In many database servers, IN() is just a synonym for multiple OR clauses, because
the two are logically equivalent. Not so in MySQL, which sorts the values in the
IN() list and uses a fast binary search to see whether a value is in the list. This is
O(log n) in the size of the list, whereas an equivalent series of OR clauses is O(n) in
the size of the list (i.e., much slower for large lists).
所以呢,IN 查询会被转变为 OR 查询,列子如下。
举个栗子
有如下简单的的 SQL:
SELECT
*
FROM
tbl1
WHERE
col3 IN (SELECT col3 FROM tbl2)
那么经过 MySQL 会先执行 SELECT col3 FROM tbl2
,假设得到三个值 '1', '2',则会被会被处理为 OR
语句:
SELECT
*
FROM
tbl1
WHERE
col3 = '1'
OR col3 = '2'
而 OR
语句实际上又会被处理成 UNION ALL
,所以最后执行的语句类似于:
SELECT
*
FROM
tbl1
WHERE
col3 = '1'
UNION ALL
SELECT
*
FROM
tbl1
WHERE
col3 = '2'
因此,如果子查询 SELECT col3 FROM tbl2
的数据量很大的话,MySQL 的解析可能比执行更耗费时间。(PS:多少数据量算多呢?这个我一直没有找到答案,应该也是和MySQL的配置相关,所以才不会有一个定值,因此建议尽量使用 EXISTS 或者 JOIN)
MySQL 可能对IN查询做的优化
书籍 高性能MySQL第三版(O'Reilly.High.Performance.MySQL.3rd.Edition.M) 有描述了 IN 查询有可能会被MySQL内部优化为 EXISTS 查询,文章路径:Chapter 6. Query Performance Optimization-->Limitations of the MySQL Query Optimizer-->Correlated Subqueries。
语句一:比如一个IN查询:
SELECT
*
FROM
sakila.film
WHERE
film_id IN ( SELECT film_id FROM sakila.film_actor WHERE actor_id = 1 );
语句二:有可能在实现的时候被分成了两次查询,先通过查询得到 film_id 在通过 IN 查询,如下所示:
SELECT GROUP_CONCAT(film_id) FROM sakila.film_actor WHERE actor_id = 1;
-- Result: 1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,970,980
SELECT * FROM sakila.film
WHERE film_id
IN(1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,970,980);
实际上呢,语句一MySQL会尝试优化为 EXISTS 查询,如下的语句,而语句二则没办法做更多的优化。应该是简单的查询可以直接优化,复杂的查询是不能够的,要不然平常直接写IN语句,而不用专门改成 EXISTS 或者 INNER JOIN 语句。
SELECT
*
FROM
sakila.film
WHERE
EXISTS ( SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id );
NOT IN 改成 NOT EXIST/LEFT JOIN
例如有如下的 NOT IN 查询:
SELECT
*
FROM
tbl1
WHERE
col3 NOT IN ( SELECT col3 FROM tbl2 )
改成 NOT EXISTS 语法:
SELECT
*
FROM
tbl1
WHERE
NOT EXISTS ( SELECT 1 FROM tbl2 WHERE tbl1.col3 = tbl2.col3 )
改成 LEFT JOIN 语法:
SELECT
*
FROM
tbl1
LEFT JOIN tbl2 ON tbl1.col3 = tbl2.col3
WHERE
tbl2.col3 IS NULL
书籍 高性能MySQL第三版(O'Reilly.High.Performance.MySQL.3rd.Edition.M) 有描述了 NOT EXISTS 与 LEFT JOIN 的对比,文章路径:Chapter 6. Query Performance Optimization-->Limitations of the MySQL Query Optimizer-->Correlated Subqueries-->When a correlated subquery is good。该部分对比了二者的执行计划,实际上是相差无几的。
NOT EXISTS 的执行计划
NOT EXISTS 查询:
EXPLAIN SELECT
film_id,
language_id
FROM
sakila.film
WHERE
NOT EXISTS ( SELECT * FROM sakila.film_actor WHERE film_actor.film_id = film.film_id ) \G
执行计划输出内容:
*************************** 1. row ***************************
id: 1
230 | Chapter 6: Query Performance Optimization
select_type: PRIMARY
table: film
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 951
Extra: Using where
*************************** 2. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: film_actor
type: ref
possible_keys: idx_fk_film_id
key: idx_fk_film_id
key_len: 2
ref: film.film_id
rows: 2
Extra: Using where; Using index
LEFT JOIN 执行计划
LEFT JOIN 查询:
EXPLAIN SELECT
film.film_id,
film.language_id
FROM
sakila.film
LEFT OUTER JOIN sakila.film_actor USING ( film_id )
WHERE
film_actor.film_id IS NULL \G
执行计划输出结果:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 951
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: film_actor
type: ref
possible_keys: idx_fk_film_id
key: idx_fk_film_id
key_len: 2
ref: sakila.film.film_id
rows: 2
Extra: Using where; Using index; Not exists
二者相差无几,LEFT JOIN 性能会略好一些,所以建议使用 LEFT JOIN。
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
欢迎转载,但请注明本文链接,谢谢你。
2018.9.16 19:50
为什么 EXISTS(NOT EXIST) 与 JOIN(LEFT JOIN) 的性能会比 IN(NOT IN) 好的更多相关文章
- Mysql Join语法解析与性能分析详解
一.Join语法概述 join 用于多表中字段之间的联系,语法如下: ... FROM table1 INNER|LEFT|RIGHT JOIN table2 ON conditiona table1 ...
- [转]Mysql Join语法解析与性能分析
转自:http://www.cnblogs.com/BeginMan/p/3754322.html 一.Join语法概述 join 用于多表中字段之间的联系,语法如下: ... FROM table1 ...
- left join, right join , inner join, join, union的意义
数据库在连接两张或以上的表来返回数据时,都会生成一张中间的临时表,然后再将临时表返回给用户left join,right join,inner join, join 与 on 配合用 select c ...
- (转)MySQL join语法解析与性能分析
文章转载的:http://www.cnblogs.com/BeginMan/p/3754322.html 一.join语法概述 join用于多表中字段之间的联系,语法如下: ... FROM tabl ...
- Hive 中的 LEFT SEMI JOIN 与 JOIN ON
hive 的 join 类型有好几种,其实都是把 MR 中的几种方式都封装实现了,其中 join on.left semi join 算是里边具有代表性,且使用频率较高的 join 方式. 1.联系 ...
- Mysql多表表关联查询 inner Join left join right join
Mysql多表表关联查询 inner Join left join right join
- SQL Left Join, Right Join, Inner Join, and Natural Join 各种Join小结
在SQL语言中,存在着各种Join,有Left Join, Right Join, Inner Join, and Natural Join等,对于初学者来说肯定一头雾水,都是神马跟神马啊,它们之间到 ...
- EF INNER JOIN,LEFT JOIN,GROUP JOIN
IQueryable<TOuter>的扩展方法中提供了 INNER JOIN,GROUP JOIN但是没有提供LEFT JOIN GROUP JOIN适用于一对多的场景,如果关联的GROU ...
- sql inner join , left join, right join , union,union all 的用法和区别
Persons 表: Id_P LastName FirstName Address City 1 Adams John Oxford Street London 2 Bush George Fift ...
- inner join ,left join ,right join 以及java时间转换
1.inner join ,left join 与 right join (from 百度知道) 例表aaid adate1 a12 a23 a3表bbid bdate1 ...
随机推荐
- 路由导航之第一个子模块(HomeModule)
git clone git@github.com:len007/my-angular2-app.git my-angular2-app 开始 一个URL = 一个页面 = 一个Component. 我 ...
- Ubuntu系统建立交叉编译环境
飞凌 FET6818核心板 解压编译器: tar zxvf arm-cortex_a9_eabi-4.7-eglibc-2.18.tar.gz -C/opt 设置默认编译环境: vi /etc/pr ...
- 给django视图类添加装饰器
要将login_required装饰到view class的dispatch方法上, 因为dispatch方法为类方法,不是单个的函数,所以需要将装饰函数的装饰器 login_required转化为装 ...
- EFCore Owned Entity Types,彩蛋乎?鸡肋乎?之鸡肋篇
鸡肋 鸡肋(Chicken ribs),现代汉语词语,出自<三国志·魏书·武帝纪>裴松之注引<九州春秋>曰:"夫鸡肋,弃之如可惜,食之无所得,以比汉中,知王欲还也.& ...
- Java获取客户端真实IP
一般情况下,我们可以使用 request 的 getRemoteAddr() 方法获取客户端实际 IP ,但是使用反向代理后,我们使用 getRemoteAddr() 是无法获取真实的 IP 的. ...
- python-数据类型练习题1
1.有变量name = "aleX leNb" 完成如下操作:移除name变量对应的值两边的空格,并输出处理结果n1 = name.strip()print(n1) 结果:aleX ...
- union、union all 、distinct的区别和用途
1.从用途上讲 它们都具有去重的效果 2.从效率上讲 distinct通常不建议使用,效率较低;union all 和union 而言,union all效率更高;原因是:union 相当于多表查询出 ...
- springboot接收delete或者put方法体参数
springboot默认配置了hiddenHttpMethodFilter(可以在springboot启动日志中看到) 因为hiddenHttpMethodFilter只会拦截get和post请求方式 ...
- python笔记26-编码规范层级目录
bin-放的可执行文件 conf-放的配置文件 lib-放的一些lib库 temp-放的零时文件 logs-日志 core-核心逻辑 data-存放数据 README-帮助文档 start_shop. ...
- 微信浏览器安卓手机video浮在最上层问题
微信浏览器安卓手机video浮在最上层问题 //x5-video-player-type="h5" x5-video-player-fullscreen="true&qu ...