MySQL中联合查询的一些优化感悟
背景
今天博主在left join的SQL语句优化中发现了一个奇怪的现象,随后在网上查阅了相关资料,现在复盘并记录下来。
描述
现在有 data_users
以及 data_posts
两张表。
data_users
表中有主键索引id
。 data_posts
表中索引 uid
,uid是外键。
现在有如下SQL语句:
select * from data_users LEFT JOIN data_posts on data_users.id = data_posts.uid;
可以看到上面结果,左表是没有命中索引的,所以进行了全表扫描。右表因为 uid
建立了索引,最后命中了索引,但是这样的效率其实比较低的,特别是在两张表数据量规模大的时候,他所扫描的基本是是左表的总行数和右表的查询结果的笛卡尔积。
接下来我们改下sql语句。
select * from data_users JOIN data_posts on data_users.id = data_posts.uid;
可以看到上面结果都命中了索引,这样效率得到了大大的提升。
那么除了这种改动之外,我对第一个sql语句中做另外的改变,得到第二个优化后的语句。
select * from data_users LEFT JOIN data_posts on data_users.id = data_posts.uid where data_users.id = 1;
可以看到上面的查询结果也全都命中了索引,那么这其中的原理究竟为何呢?
MySQL中join的原理
在网上查询了mysql实现join的原理,原来 MySQL
内部采用了一种叫做 nested loop join
的算法,该算法就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果。如果还有第三个参与了join,则再通过前两个表的结果集作为循环基础数据,再一次通过循环查询条件到第三个表中查询数据,如此往复,基本上Mysql采取容易理解的算法来实现join。所以驱动表的选择非常重要,驱动表的数据量小可以显著降低扫描的行数。
那么为什么一版情况下join的效率要高于left join很多呢,其实一般情况下参与联合查询的两张表都会一大一小,如果是join,在没有其他过滤条件的情况下,会选择用小表作为驱动表,但是left join一般因为本身的特性决定了会用大表做驱动表,这样下来效率就差了不少。
结束语
在了解join的基本原理后,在我们使用join后,mysql会使用 data_posts
的结果集作为驱动表,而该结果集已经命中了索引,因此效率得到了显著的提升。
而第二个优化后的sql语句,因为加了where子句,虽然因为left join的特性,选择了大表 data_users
作为驱动表,但本身经过过滤后,数量大大减少且命中了索引。因此该语句的效率也得到了显著的提升。