`
luxury_zh
  • 浏览: 72716 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

从一个MySQL left join优化的例子加深对查询计划的理解

阅读更多
   今天遇到一个left join优化的问题,搞了一下午,中间查了不少资料,对MySQL的查询计划还有查询优化有了更进一步的了解,做一个简单的记录:

select c.* from hotel_info_original c
left join hotel_info_collection h
on c.hotel_type=h.hotel_type and c.hotel_id =h.hotel_id
where h.hotel_id is null

   这个sql是用来查询出c表中有h表中无的记录,所以想到了用left join的特性(返回左边全部记录,右表不满足匹配条件的记录对应行返回null)来满足需求,不料这个查询非常慢。先来看查询计划:



   rows代表这个步骤相对上一步结果的每一行需要扫描的行数,可以看到这个sql需要扫描的行数为35773*8134,非常大的一个数字。本来c和h表的记录条数分别为40000+和10000+,这几乎是两个表做笛卡尔积的开销了(select * from c,h)。
于是我上网查了下MySQL实现join的原理,原来MySQL内部采用了一种叫做 nested loop join的算法。Nested Loop Join 实际上就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果。如果还有第三个参与 Join,则再通过前两个表的 Join 结果集作为循环基础数据,再一次通过循环查询条件到第三个表中查询数据,如此往复,基本上MySQL采用的是最容易理解的算法来实现join。所以驱动表的选择非常重要,驱动表的数据小可以显著降低扫描的行数。
那么为什么一般情况下join的效率要高于left join很多?很多人说不明白原因,只人云亦云,我今天下午感悟出来了一点。一般情况下参与联合查询的两张表都会一大一小,如果是join,在没有其他过滤条件的情况下MySQL会选择小表作为驱动表,但是left join一般用作大表去join小表,而left join本身的特性决定了MySQL会用大表去做驱动表,这样下来效率就差了不少,如果我把上面那个sql改成
select c.* from hotel_info_original c
join hotel_info_collection h
on c.hotel_type=h.hotel_type and c.hotel_id =h.hotel_id
查询计划如下:



     很明显,MySQL选择了小表作为驱动表,再配合(hotel_id,hotel_type)上的索引瞬间降低了好多个数量级。。。。。
另外,我今天还明白了一个关于left join 的通用法则,即:如果where条件中含有右表的非空条件(除开is null),则left join语句等同于join语句,可直接改写成join语句。
后记:
随着查看MySQL reference manual对这个问题进行了更进一步的了解。MySQL在执行join时会把join分为system/const/eq_ref/ref/range/index/ALl等好几类,连接的效率从前往后
依次递减,对于我的第一个sql,连接类型是index,所以几乎是全表扫描的效果。但是我很奇怪我在(hotel_id,hotel_type)两列上声明了unique key,根据官方文档连接类型应该是eq_ref才对,
     这个问题一直困扰了我两天,在google和stackoverflow上都没有找到能够解释这个问题的文章,莫非我这个问题无解了?抱着解决这个问题的决心今天又翻看了一遍MySQL官方文档
关于优化查询的部分,看到了这样一句:这里的一个问题是MySQL能更高效地在声明具有相同类型和尺寸的列上使用索引。我感觉我找到了问题所在,于是我将original和 collection表的(hotel_type,hotel_id)的encoding和collation(决定字符比较的规则)全部改成统一的utf8_general_ci,然后再次运行第一条sql的查询计划,得到如下结果:



     连接类型已经由index优化到了ref,如果将hotel_type申明为not null可以优化到eq_ref,不过这里影响不大了,优化后这条sql能在0.01ms内运行完。

     那么如何优化left join:
1、条件中尽量能够过滤一些行将驱动表变得小一点,用小表去驱动大表

2、右表的条件列一定要加上索引(主键、唯一索引、前缀索引等),最好能够使type达到range及以上(ref,eq_ref,const,system)

3、无视以上两点,一般不要用left join~~!


  • 大小: 33.2 KB
  • 大小: 32.1 KB
  • 大小: 39 KB
0
0
分享到:
评论
1 楼 IWSo 2016-03-11  
select c.* from hotel_info_original c
join hotel_info_collection h
on c.hotel_type=h.hotel_type and c.hotel_id =h.hotel_id
比左连接SQL语句:
select c.* from hotel_info_original c
left join hotel_info_collection h
on c.hotel_type=h.hotel_type and c.hotel_id =h.hotel_id
where h.hotel_id is null
的速度快很多。但是两个SQL查询的结果不一样吧?
第一个SQL语句查询的是两个表的交集;第二个SQL是将表1的全部数据查出,然后连接表2,然后使用where条件将表二中等于null的数据过滤掉。
所以,查询结果不一致的话,比较效率没有意义

相关推荐

    MySQL必知必会(高清带书签)

    9. **视图**:学习如何创建和使用视图,视图可以简化复杂的查询,提供一个虚拟的表接口。 10. **存储过程与函数**:学习编写存储过程和用户自定义函数,以实现数据库的复杂逻辑和提高代码复用性。 11. **事务处理*...

    mysql必知必会

    这些脚本可以作为实际操作的例子,帮助读者加深对MySQL语法的理解。 学习"mysql必知必会"不仅能够让你掌握数据库的基本操作,还能让你在面对复杂的数据处理需求时游刃有余。通过不断实践和学习,你可以成为数据库...

    mysql连接查询(左连接,右连接,内连接)

    本文将详细讲解三种基本的连接类型:INNER JOIN(内连接)、LEFT JOIN(左连接)以及RIGHT JOIN(右连接),并提供实例代码以加深理解。 1. INNER JOIN(内连接) INNER JOIN 返回两个表中匹配字段的记录。这意味着...

    SQL学习资料(包含所有常用的语句例子)

    4. **连接查询**:JOIN操作将两个或更多表中的数据结合在一起,如INNER JOIN、LEFT JOIN、RIGHT JOIN和FULL OUTER JOIN。 5. **子查询**:在SELECT语句中嵌套另一个SELECT语句,用于返回满足特定条件的结果集。 6....

    sql练习语句,培训用,比较典型的例子好用

    SQL(Structured Query Language)是一种用于管理和操作关系数据库的标准语言,广泛应用于数据的增删改查、数据查询优化、数据处理及存储过程等场景。在这个"sql练习语句,培训用,比较典型的例子好用"的资源中,...

    sql方面的代码,可以学习一下

    学习时,要注意如何插入整个记录或指定列的值,以及如何从另一个查询结果中插入数据。 3. **UPDATE语句**:用于修改已存在的记录。掌握如何根据特定条件更新多行数据是很关键的。 4. **DELETE语句**:用于删除表中...

    sql.rar_笔试

    2. **练习案例分析**:通过实际例子加深对SQL的理解,尤其是复杂查询和联接操作。 3. **时间空间复杂度**:理解SQL语句的时间和空间复杂度,优化查询性能。 4. **异常处理**:了解如何处理SQL语句执行时可能出现的...

    SQL语言教程&demo.zip

    5. **联接操作(JOIN)**:SQL支持内连接(INNER JOIN)、左连接(LEFT JOIN)、右连接(RIGHT JOIN)和全连接(FULL JOIN),用于合并多个表的数据。 6. **子查询**:在主查询中嵌套的查询称为子查询,它可以作为...

    mysql自联去重的一些笔记记录

    1. LEFT JOIN语法:在MySQL中,LEFT JOIN用于从左表(主查询表)返回所有的行,即使右表(联结表)中没有匹配的行。这使得我们可以选择左表中所有的记录,并根据与右表的匹配情况来决定是否添加右表中的记录。若右表...

Global site tag (gtag.js) - Google Analytics