网络上有大量的资料说起将 IN 改为 JOIN 或者 exist,而后修改完成以后确实变快了,但是为何会变快呢?IN、EXIST、JOIN 在 MySQL 中的实现逻辑如何理解呢?本文也是比较粗浅的作一些介绍,知道了 MySQL 的大概执行逻辑,也方便理解。本文绝大多数内容来自:高性能MySQL第三版(O'Reilly.High.Performance.MySQL.3rd.Edition.M),还有一部分来自于网络,还有的来自于本身的理解,如下的内容有引用的都会作标准,若有雷同,纯属巧合。html
例若有以下的 IN 查询:java
SELECT * FROM tbl1 WHERE col3 IN ( SELECT col3 FROM tbl2 )
若是子查询 select id from t2
数据量比较大的状况下,则会很慢,从网络找找答案,就知道每每是建议修改成:mysql
SELECT * FROM tbl1 WHERE EXISTS ( SELECT 1 FROM tbl2 WHERE tbl1.col3 = tbl2.col3 )
或者改为 INNER JOIN 形式:sql
SELECT * FROM tbl1 INNER JOIN tbl2 ON tbl1.col3 = tbl2.col3
确实这两种优化是可行的。不过整体来讲更推荐 INNER JOIN,下面章节也会说起。网络
一下内容摘抄自 高性能MySQL第三版(O'Reilly.High.Performance.MySQL.3rd.Edition.M),文章目录:Query Performance Optimization-->Query Execution Basics-->The Query optimizer Process-->MySQL's join execution strategyapp
简单的 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
ui
简单的例子:rest
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 差很少。
没可以找到伪代码,我的以为应该执行逻辑和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。
从官网与知名书籍中找到了以下的信息。从官网文档: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第三版(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 查询:
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 查询:
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 查询:
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