SQL语句的执行计划和性能优化有什么关系?

一般开发一个系统,都是先设计表结构,表结构必须满足业务需求,然后写代码,代码都写好之后,再根据你的代码如何查询表的,来设计里面的索引,考虑设计几个索引,是不是联合索引,选择哪些字段,字段顺序如何排列,才能让查询语句都用上索引。
根据查询语句设计良好的索引,让所有查询都尽可能用上索引,这本身就是一种SQL优化的技巧,但是他仅仅只是其一罢了,并不能说掌握这个,就掌握了所有的SQL优化技巧。反过来说,SQL优化技巧中包含了我们之前讲的设计索引以及让SQL用上索引,但是SQL优化还有很多其他的东西。
实际上有时候往往你会发现自己的数据库里有很多表,每个表的数据量也不小,然后写出来的SQL也比较复杂,各种关联和嵌套子查询,搞的人看的都眼晕,然后表面上看起来这个SQL部分用上了索引,结果性能还是差,差,差,这是为什么呢?所以说,基础的以及日常的SQL优化就是设计好索引,让一般不太复杂的普通查询都用上索引,但是针对复杂表结构和大数据量的上百行复杂SQL的优化,必须得建立在你先懂这个复杂SQL是怎么执行的。
你有那么多的数据表,每个表都有一个聚簇索引,聚簇索引的叶子就是那个表的真实数据,同时每个表还设计了一些二级索引,那么上百行的复杂SQL跑起来的时候到底是如何使用各个索引,如何读取数据的?这个SQL语句(不管是简单还是复杂),在实际的MySQL底层,针对磁盘上的大量数据表、聚簇索引和二级索引,如何检索查询,如何筛选过滤,如何使用函数,如何进行排序,如何进行分组,到底怎么能把你想要的东西查出来,这个过程就是一个很重要的东西:执行计划!
也就是说,每次你提交一个SQL给MySQL,他内核里的查询优化器,都会针对这个SQL语句的语义去生成一个执行计划,这个执行计划就代表了,他会怎么查各个表,用哪些索引,如何做排序和分组,看懂这个执行计划,你就学会了真正的SQL优化的一半了!当你看懂执行计划之后,还能根据他的实际情况去想各种办法改写你的SQL语句,改良你的索引设计,进而优化SQL语句的执行计划,最终让SQL语句的性能得到提升,这个就是所谓的SQL调优。

MySQL单表查询执行计划包含哪些内容?

假设你写一个select from table where id=x,或者select from table where name=x的语句,直接就可以通过聚簇索引或者二级索引+聚簇索引回源,轻松查到你要的数据,这种根据索引直接可以快速查找数据的过程,在执行计划里称之为const,意思就是性能超高的常量级的。
所以你以后在执行计划里看到const的时候,就知道他就是直接通过索引定位到数据,速度极快,这就是const的意思。但是这里有一个要点,你的二级索引必须是唯一索引,才是属于const方式的,也就是说你必须建立unique key唯一索引,保证一个二级索引的每一个值都是唯一的,才可以。那么如果你是一个普通的二级索引呢?就是个普通的KEY索引,这个时候如果你写一个select from table where name=x的语句,name是个普通二级索引,不是唯一索引,那么此时这种查询速度也是很快的,他在执行计划里叫做ref。
如果你是包含多个列的普通索引的话,那么必须是从索引最左侧开始连续多个列都是等值比较才可以是属于ref方式,就是类似于select
from table where name=x and age=x and xx=xx,然后索引可能是个KEY(name,age,xx)。然后一个例外,就是如果你用name IS NULL这种语法的话,即使name是主键或者唯一索引,还是只能走ref方式。
但是如果你是针对一个二级索引同时比较了一个值还有限定了IS NULL,类似于select from table where name=x or name IS NULL,那么此时在执行计划里就叫做ref_or_null。说白了,就是在二级索引里搜你要的值以及是NULL的值,然后再回源去聚簇索引里查罢了,因为同时有索引等值比较和NULL值查询,就叫做ref_or_null了,其实也没啥。
那这个ref就说完了,我们换个角度看,假设你以后在分析执行计划的时候看到了const,那是什么?对,肯定是通过主键或者唯一索引的访问,速度超高。
如果你看到了ref是什么意思?对,就是用了普通的索引,或者用主键/唯一索引搞了一个IS NULL/IS NOT NULL。
不管怎么说,只要你看到const或者ref,那恭喜你,说明起码这部分执行速度是很快的!
说说range这个东西,这个东西顾名思义,其实就是你SQL里有范围查询的时候就会走这个方式。比如写一个SQL是select
from table where age>=x and age <=x,假设age就是一个普通索引,此时就必然利用索引来进行范围筛选,一旦利用索引做了范围筛选,那么这种方式就是range。
别担心,他们都是说基于索引在查询,总之都是走索引,所以一般问题不是太大,除非你通过索引查出来的数据量太多了,比如上面那个范围筛选,一下子查出来10万条数据,那不是想搞死MySQL么!是不是!
下面我们来讲一种比较特殊的数据访问方式,就是index,可能有的人看到这个index,天真的认为,这不就是通过索引来获取数据么,从索引根节点开始一通二分查找,不停的往下层索引跳转,就可以了,速度超快,感觉上跟ref或者range是一回事。那你就大错特错了!
假设我们有一个表,里面完整的字段联合索引是KEY(x1,x2,x3),好,现在我们写一个SQL语句是select x1,x2,x3 from table where x2=xxx,相信大多数同学看到这里,都会觉得,完蛋了,x2不是联合索引的最左侧的那个字段啊!对的,这个SQL是没办法直接从联合索引的索引树的根节点开始二分查找,快速一层一层跳转的,那么他会怎么执行呢?不知道大家是否发现这个SQL里要查的几个字段,就是联合索引里的几个字段,巧了!所以针对这种SQL,在实际查询的时候,就会直接遍历KEY(x1,x2,x3)这个联合索引的索引树的叶子节点。
聚簇索引的叶子节点放的是完整的数据页,里面包含完整的一行一行的数据,联合索引的叶子节点放的也是页,但是页里每一行就x1、x2、x3和主键的值!所以此时针对这个SQL,会直接遍历KEY(x1,x2,x3)索引树的叶子节点的那些页,一个接一个的遍历,然后找到 x2=xxx 的那个数据,就把里面的x1,x2,x3三个字段的值直接提取出来就可以了!这个遍历二级索引的过程,要比遍历聚簇索引快多了,毕竟二级索引叶子节点就包含几个字段的值,比聚簇索引叶子节点小多了,所以速度也快!
也就是说,此时只要遍历一个KEY(x1,x2,x3)索引就可以了,不需要回源到聚簇索引去!针对这种只要遍历二级索引就可以拿到你想要的数据,而不需要回源到聚簇索引的访问方式,就叫做index访问方式!
没错,所以理解执行计划的前提,是对索引结构和使用索引的原理有一个透彻的理解,在这个基础之上,很容易就可以理解各种各样的执行计划里的访问方式了 ,脑子里甚至直接可以知道不同的访问方式在图里的执行路径。现在我们思考一下,之前说的const、ref和range,本质都是基于索引树的二分查找和多层跳转来查询,所以性能一般都是很高的,然后接下来到index这块,速度就比上面三种要差一些了,因为他是走遍历二级索引树的叶子节点的方式来执行了,那肯定比基于索引树的二分查找要慢多了,但是还是比全表扫描好一些的。
另外最次的一种就是all了,all意思就是直接全表扫描,扫描你的聚簇索引的所有叶子节点,也就是一个表里一行一行数据去扫描,如果一个表就几百条数据那还好,如果是有几万条,或者几十万,几百万数据,全表扫描基本就得跪了。

写出各种SQL语句的时候,会用什么执行计划?

SQL语句你写出来之后,会用什么样的执行计划。首先看一个SQL语句:select from table where x1=xx and x2>=xx,这个SQL语句要查一个表,用了x1和x2两个字段,此时有人可能会说了,要是你对x1和x2建了一个联合索引,那不就直接可以通过索引去扫描了?
但是万一要是你建的索引是两个呢?比如(x1,x3),(x2,x4),你建了两个联合索引,此时你这个SQL只能选择其中一个索引去用,此时会选择哪个呢?这里MySQL负责生成执行计划的查询优化器,一般会选择在索引里扫描行数比较少的那个条件。比如说x1=xx,在索引里只要做等值比较,扫描数据比较少,那么可能就会挑选x1的索引,做一个索引树的查找,在执行计划里,其实就是一个ref的方式,找到几条数据之后,接着做一个回表,回到聚簇索引里去查出每条数据完整数据,接着加载到内存里,根据每条数据的x2字段的值,根据x2>=xx条件做一个筛选。这就是面对两个字段都能用索引的时候如何选择,以及如何进行处理的方式。
接着我们再来考虑另外一种情况,就是:select
from table where x1=xx and c1=xx and c2>=xx and c3 IS NOT NULL
其实我们平时经常会写出来类似这样的SQL语句,就是在一个SQL的所有筛选条件里,就一个x1是有索引的,其他字段都是没有索引的。这种情况其实也是非常常见的,一般我们在写好一个系统之后,针对所有的SQL分析时,当然不可能针对所有的SQL里的每一个where里的字段都加一个索引,那是不现实的,最终我们只能在所有的SQL语句里,抽取部分经常在where里用到的字段来设计两三个联合索引。
所以在这种情况下,必然很多SQL语句里,可能where后的条件有好几个,结果就一个字段可以用到索引的,此时查询优化器生成的执行计划,就会仅仅针对x1字段走一个ref访问,直接通过x1字段的索引树快速查找到指定的一波数据。接着对这波数据都回表到聚簇索引里去,把每条数据完整的字段都查出来,然后都加载到内存里去。接着就可以针对这波数据的c1、c2、c3字段按照条件进行筛选和过滤,最后拿到的就是符合条件的数据了。所以你的x1索引的设计,必然尽可能是要让x1=xx这个条件在索引树里查找出来的数据量比较少,才能保证后续的性能比较高。
我们来看一个比较奇特的SQL语句以及特殊的执行计划,之前我们都是一般一个SQL语句只能用到一个二级索引,但是有一些特殊的情况下,可能会对一个SQL语句用到多个二级索引,这是怎么回事呢?
比如有这么一个SQL:select * from table where x1=xx and x2=xx,然后x1和x2两个字段分别都有一个索引,其实也有一定的可能会让查询优化器生成一个执行计划,执行计划里,就先对x1字段的索引树进行查找,查出一波数据,接着对x2的索引树查出一波数据,然后对两波数据,按照主键值做一个交集。这个交集就是符合两个条件的数据了,接着回表到聚簇索引去查完整数据就可以了。
但是其实之前我们对这种情况一直说的是,选择x1或者x2其中一个字段的索引,就查一个字段的索引,找出一波数据,接着直接回表到聚簇索引查完整数据,然后根据另外一个字段的值进行过滤就可以了。那么到底什么情况下,会直接对两个字段的两个索引一起查,然后取交集再回表到聚簇索引呢?也就是什么情况下可能会对一个SQL执行的时候,一下子查多个索引树呢?
假设就上面那个SQL语句吧,比如你x1和x2两个字段,如果你先查x1字段的索引,一下子弄出来上万条数据,这上万条数据都回表到聚簇索引查完整数据,再根据x2来过滤,你有没有觉得效果不是太好?那如果说同时从x2的索引树里也查一波数据出来,做一个交集,一下子就可以让交集的数据量变成几十条,再回表查询速度就很快了。一般来说,查索引树速度都比较快,但是到聚簇索引回表查询会慢一些。
所以如果同时查两个索引树取一个交集后,数据量很小,然后再回表到聚簇索引去查,此时会提升性能。但是如果要在一个SQL里用多个索引,那有很多硬性条件的要求,比如说如果有联合索引,你必须把联合索引里每个字段都放SQL里,而且必须都是等值匹配;或者是通过主键查询+其他二级索引等值匹配,也有可能会做一个多索引查询和交集。
在执行SQL语句的时候,有可能是会同时查多个索引树取个交集,再回表到聚簇索引的,这个可能性是有的。
那么其实如果你在SQL里写了类似x1=xx or x2=xx的语句,也可能会用多个索引,只不过查多个大索引树之后,会取一个并集,而不是交集罢了。

多表关联的SQL语句到底是如何执行的?

我们往往从驱动表里查出来一波数据之后,要对每一条数据都循环一次去被驱动表里查询数据,所以万一你要是被驱动表的索引都没建好,总不能每次都全表扫描吧?另外一个,刚开始对你的驱动表根据WHERE条件进行查询的时候,也总不能全表扫描吧?这也是一个问题!
所以说,为什么有的时候多表关联很慢呢?答案就在这里了,你两个表关联,先从驱动表里根据WHERE条件去筛选一波数据,这个过程如果你没给驱动表加索引,万一走一个all全表扫描,岂不是速度很慢?其次,假设你好不容易从驱动表里扫出来一波数据,接着又来一个for循环一条一条去被驱动表里根据ON连接条件和WHERE筛选条件去查,万一你对被驱动表又没加索引,难道又来几十次或者几百次全表扫描?那速度岂不是慢的跟蜗牛一样了!
所以说,通常而言,针对多表查询的语句,我们要尽量给两个表都加上索引,索引要确保从驱动表里查询也是通过索引去查找,接着对被驱动表查询也通过索引去查找。如果能做到这一点,你的多表关联语句性能就会很高!

MySQL是如何根据成本优化选择执行计划的?

我们先了解一下MySQL里的成本是什么意思,简单来说,跑一个SQL语句,一般成本是两块,首先是那些数据如果在磁盘里,你要不要从磁盘里把数据读出来?这个从磁盘读数据到内存就是IO成本,而且MySQL里都是一页一页读的,读一页的成本的约定为1.0。然后呢,还有一个成本,那就是说你拿到数据之后,是不是要对数据做一些运算?比如验证他是否符合搜索条件了,或者是搞一些排序分组之类的事,这些都是耗费CPU资源的,属于CPU成本,一般约定读取和检测一条数据是否符合条件的成本是0.2.
这个所谓1.0和0.2就是他自定义的一个成本值,代表的意思就是一个数据页IO成本就是1.0,一条数据检测的CPU成本就是0.2,就这个意思罢了。
然后呢,当你搞一个SQL语句给MySQL的时候,比如:select from t where x1=xx and x2=xx
此时你有两个索引,分别是针对x1和x2建立的,就会先看看这个SQL可以用到哪几个索引,此时发现x1和x2的索引都能用到,他们俩索引就是possible keys。
接着会针对这个SQL计算一下全表扫描的成本,这个全表扫描的话就比较坑了,因为他是需要先磁盘IO把聚簇索引里的叶子节点上的数据页一页一页都读到内存里,这有多少数据页就得耗费多少IO成本,接着对内存里的每一条数据都判断是否符合搜索条件的,这有多少条数据就要耗费多少CPU成本。
所以说,此时就得计算一下这块成本有多少,怎么算呢?简单,一个命令:show table status like “表名”
可以拿到你的表的统计信息,你在对表进行增删改的时候,MySQL会给你维护这个表的一些统计信息,比如这里可以看到rows和data_length两个信息,不过对于innodb来说,这个rows是估计值。rows就是表里的记录数,data_length就是表的聚簇索引的字节数大小,此时用data_length除以1024就是kb为单位的大小,然后再除以16kb(默认一页的大小),就是有多少页,此时知道数据页的数量和rows记录数,就可以计算全表扫描的成本了。IO成本就是:数据页数量
1.0 + 微调值,CPU成本就是:行记录数 0.2 + 微调值,他们俩相加,就是一个总的成本值,比如你有数据页100个,记录数有2万条,此时总成本值大致就是100 + 4000 = 4100,在这个左右。
除了全表扫描之外,还可能多个索引都可以使用,但是当然同时一般只能用一个索引,所以不同索引的使用成本都得计算一下。这个使用索引访问数据的方式,其实很简单,除非你直接根据主键查,那就直接走一个聚簇索引就ok了,否则普通索引,一般都是两步走,先从二级索引查询一波数据,再根据这波数据的主键去聚簇索引回表查询。
这个过程的成本计算方法稍微有点特别,首先,在二级索引里根据条件查一波数据的IO成本,一般是看你的查询条件涉及到几个范围,比如说name值在25~100,250~350两个区间,那么就是两个范围,否则name=xx就仅仅是一个范围区间。一般一个范围区间就粗暴的认为等同于一个数据页,所以此时可能一般根据二级索引查询的时候,这个IO成本都会预估的很小,可能就是1
1.0 = 1,或者是n 1.0 = n,基本就是个位数这个级别。
但是到此为止,还仅仅是通过IO读取了二级索引的数据页而已,这仅仅是二级索引读取的IO成本,但是二级索引数据页到内存里以后,还得根据搜索条件去拿出来一波数据,拿这波数据的过程就是根据搜索条件在二级索引里搜索的过程。此时就要估算从二级索引里读取符合条件的数据的成本了,这需要估算一下在二级索引里会查出多少条数据,总之呢,他会根据一个不是怎么太准确的算法去估算一下根据查询条件可能会在二级索引里查出多少条数据来。
估算出来之后,比如估算可能会查到100条数据,此时从二级索引里查询数据的CPU成本就是100
0.2 + 微调值,总之就是20左右而已。接着你拿到100条数据之后,就得回表到聚簇索引里去查询完整数据,此时先估算回表到聚簇索引的IO成本,这里比较粗暴的直接默认1条数据就得回表到聚簇索引查询一个数据页,所以100条数据就是100个数据页的IO成本,也就是100 1.0 + 微调值,大致是100左右。
接着因为在二级索引里搜索到的数据是100条,然后通过IO成本最多回表到聚簇索引访问100个数据页之后,就可以拿到这100条数据的完整值了,此时就可以针对这100条数据去判断,他们是否符合其他查询条件了,这里耗费的CPU成本就是100
0.2 + 微调值,就是20左右。
把上面的所有成本都加起来,就是1 + 20 + 100 + 20 = 141,这就是使用一个索引进行查询的成本的计算方法,假设你直接根据主键查询,那么也参考上述估算过程就可以了,那就不过是仅仅查询一个聚簇索引罢了。总之,全表扫描发现成本是4100左右,根据索引查找可能就141,所以,很多时候,使用索引和全表扫描,他的成本差距是非常之大的。所以一般就会针对全表扫描和各个索引的成本,都进行估算,然后比较一下,选择一个成本最低的执行计划。
其实多表查询的执行计划选择思路,基本跟单表查询的执行计划选择思路是类似的,单表查询的时候,主要就是对这个表的多种访问方式(全表查询 ,各个索引查询)来根据一定的公式计算出来每种访问方式的成本,接着选择一个成本最低的访问方式,那么就可以确定下来这个表怎么访问了。要在一个查询执行之前,就可以针对不同的访问方法精准计算他的成本,那是根本不现实的,最后只能是根据一些相对较为简单粗暴的办法,大致估算一下,估算结果可能不是太准确,但是也没办法了,反正算出来也就这么比较就是了。
那么接着如果我们要看看多表关联的成本计算访问和执行计划选择方式,那就很简单了。
select * from t1 join t2 on t1.x1=t2.x1 where t1.x2=xxx and t1.x3=xxx and t2.x4=xxx and t2.x5=xxx
一般来说,都会先选择一个驱动表,比如t1作为驱动表,此时就需要根据t1.x2=xxx和t1.x3=xxx这个条件从表里查询一波符合条件的数据出来,此时就有一个问题了,这里用到了t1的两个字段来筛选数据,可能x2和x3字段都建了索引了,此时到底选择哪个索引呢?或者干脆直接就是全表扫描?此时就会按照之前讲的那套方法来计算针对t1表查询的全表扫描和不同索引的成本,选择一个针对t1表的最佳访问方式,用最低成本从t1表里查出符合条件的数据来,接着就根据这波数据得去t2表里查数据,按照连接条件t1.x1=t2.x1去查,同时要符合t2.x4=xxx和t2.x5=xxx这两个条件。
此时一样会根据之前讲解的办法去估算,针对t2表的全表扫描以及基于x4、x5、x1几个字段不同索引的访问的成本,挑选一个成本最低的方法,然后从t2表里把数据给查找出来,就可以,这就完成了多表关联!所以其实多表关联的成本估算以及执行计划选择方式,跟单表关联基本上是差不多的,只不过多表关联要多查几个表罢了。

MySQL是如何基于各种规则去优化执行计划的?

MySQL在执行一些相对较为复杂的SQL语句的时候,因为他有时候可能会觉得你写的SQL一点都不好,直接按你的SQL生成的执行计划效率还是不够高,需要自动帮你改改。

首先呢,要是MySQL觉得你的SQL里有很多括号,那么无关紧要的括号他会给你删除了,其次比如你有类似于i = 5 and j > i这样的SQL,就会改写为i = 5 and j > 5,做一个常量替换。
还有比如x = y and y = k and k = 3这样的SQL,都会给你优化成x = 3 and y = 3 and k = 3,本质也是做个常量替换。或者是类似于什么b = b and a = a这种一看就是乱写的SQL,一看就是没意义的,就直接给你删了。这些SQL的改写,他本质都是在优化SQL语句的清晰语义,方便后续在索引和数据页里进行查找。

还有一些是比较有意思的改写,比如下面的SQL语句:select from t1 join t2 on t1.x1=t2.x1 and t1.id=1
这个SQL明显是针对t1表的id主键进行了查询,同时还要跟t2表进行关联,其实这个SQL语句就可能在执行前就先查询t1表的id=1的数据,然后直接做一个替换,把SQL替换为:
select t1表中id=1的那行数据的各个字段的常量值, t2.
from t1 join t2 on t1表里x1字段的常量值=t2.x1
上面的SQL就是直接把t1相关的字段都替换成了提前查出来的id=1那行数据的字段常量值了。

子查询是如何执行的,以及他的执行计划是如何优化的。比如说类似于下面的SQL语句:select from t1 where x1 = (select x1 from t2 where id=xxx)
也就是说上面的SQL语句在执行的时候,其实会被拆分为两个步骤:第一个步骤先执行子查询,也就是:select x1 from t2 where id=xxx,直接根据主键定位出一条数据的x1字段的值。接着再执行select
from t1 where x1=子查询的结果值,这个SQL语句。这个第二个SQL执行,其实也无非就是跟之前讲的单表查询的方式是一样的,其实大家看到最后会发现,这个SQL语句最核心的就是单表查询的几种执行方式,其他的多表关联,子查询,这些都是差不多这个意思。最多就是在排序、分组聚合的时候,可能有的时候会直接用上索引,有的时候用不上索引就会基于内存或者临时磁盘文件执行。

另外还有一种子查询,就是:select * from t1 where x1 = (select x1 from t2 where t1.x2=t2.x2)
这种时候,你会发现子查询里的where条件依赖于t1表的字段值,所以这种查询就会效率很低下,他需要遍历t1表里每一条数据,对每一条数据取出x2字段的值,放到子查询里去执行,找出t2表的某条数据的x1字段的值,再放到外层去判断,是否符合跟t1表的x1字段匹配。

我们重点来看一下IN语句结合子查询的一个优化手段,假设有如下的一个SQL语句:select * from t1 where x1 in (select x2 from t2 where x3=xxx)
这个SQL语句就是典型的一个子查询运用,子查询查一波结果,然后判断t1表哪些数据的x1值在这个结果集里。这个可能大家会想当然的认为先执行子查询,然后对t1表再进行全表扫描,判断每条数据是否在这个子查询的结果集里,但是这种方式其实效率是非常低下的。所以其实对于上述的子查询,执行计划会被优化为,先执行子查询,也就是select x2 from t2 where x3=xxx这条SQL语句,把查出来的数据都写入一个临时表里,也可以叫做物化表,意思就是说,把这个中间结果集进行物化。这个物化表可能会基于memory存储引擎来通过内存存放,如果结果集太大,则可能采用普通的b+树聚簇索引的方式放在磁盘里。但是无论如何,这个物化表都会建立索引,所以大家要清楚,这波中间结果数据写入物化表是有索引的。
接着大家可能会想,此时是不是全表扫描t1表,对每条数据的x1值都去物化表里根据索引快速查找一下是否在这个物化表里?如果是的话,那么就符合条件了。但是这里还有一个优化的点,那就是他可以反过来思考。也就是说,假设t1表的数据量是10万条,而物化表的数据量只有500条,那么此时完全可以改成全表扫描物化表,对每个数据值都到t1表里根据x1这个字段的索引进行查找,查找物化表的这个值是否在t1表的x1索引树里,如果在的话,那么就符合条件了。所以基于IN语句的子查询执行方式,实际上会在底层被优化成如上所述。

我们再来看看MySQL里对子查询的执行计划进行优化的一种方式,就是semi join,也就是半连接。
这个半连接是什么意思呢,其实就是假设你有一个子查询语句:select from t1 where x1 in (select x2 from t2 where x3=xxx),此时其实可能会在底层把他转化为一个半连接,有点类似于下面的样子:select t1. from t1 semi join t2 on t1.x1=t2.x2 and t2.x3=xxx
当然,其实并没有提供semi join这种语法,这是MySQL内核里面使用的一种方式,上面就是给大家说那么个意思,其实上面的semi join的语义,是和IN语句+子查询的语义完全一样的,他的意思就是说,对于t1表而言,只要在t2表里有符合t1.x1=t2.x2和t2.x3=xxx两个条件的数据就可以了,就可以把t1表的数据筛选出来了。

当然,其实还是要给大家提醒一句,在互联网公司里,我们比较崇尚的是尽量写简单的SQL,复杂的逻辑用Java系统来实现就可以了,SQL能单表查询就不要多表关联,能多表关联就尽量别写子查询,能写几十行SQL就别写几百行的SQL,多考虑用Java代码在内存里实现一些数据就的复杂计算逻辑,而不是都放SQL里做。其实一般的系统,只要你SQL语句尽量简单,然后建好必要的索引,每条SQL都可以走索引,数据库性能往往不是什么大问题,当然SQL高级调优也是程序员必须掌握的。

通过explain命令得到的SQL执行计划

平时我们只要用类似于:explain select * from table,这种SQL前面加一个explain命令,就可以轻松拿到这个SQL语句的执行计划。首先,当你执行explain命令之后,拿到的执行计划可能是类似下面这样的东西:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|+——+——————-+———-+——————+———+———————-+———+————-+———+———+—————+————————+|
1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
那所谓的id、select_type、table、partitions、type之类的东西了吗,其实这些就是所谓的执行计划里包含的东西。大致来说,如果是一个简单的单表查询,可能这里就只有一条数据,也就是代表了他是打算如何访问这一个表而已。但是如果你的SQL语句极为的复杂,可能这里会有很多条数据,因为一个复杂的SQL语句的执行是要拆分为很多步骤的,比如先访问表A,接着搞一个排序,然后来一个分组聚合,再访问表B,接着搞一个连接,类似这样子。
好,那么接下来我们就先来研究一下这个所谓的执行计划里包含的各个字段都是什么意思,首先是id这个东西。
这个id呢,就是说每个SELECT都会对应一个id,其实说穿了,就是一个复杂的SQL里可能会有很多个SELECT,也可能会包含多条执行计划,每一条执行计划都会有一个唯一的id,这个没啥好说的。
select_type,顾名思义,说的就是这一条执行计划对应的查询是个什么查询类型,table就是表名,意思是要查询哪个表,partitions是表分区的概念。
type,就是比较关键了,针对当前这个表的访问方法,比如说const、ref、range、index、all之类的,分别代表了使用聚簇索引、二级索引、全表扫描之类的访问方式。
possible_keys,这也很关键,他是跟type结合起来的,意思就是说你type确定访问方式了,那么到底有哪些索引是可供选择,可以使用的呢,这都会放这里。key,就是在possible_keys里实际选择的那个索引,而key_len就是索引的长度。
ref,就是使用某个字段的索引进行等值匹配搜索的时候,跟索引列进行等值匹配的那个目标值的一些信息。rows,是预估通过索引或者别的方式访问这个表的时候,大概可能会读取多少条数据。filtered,就是经过搜索条件过滤之后的剩余数据的百分比。extra是一些额外的信息,不是太重要。
所以其实在SQL调优的时候,核心就是分析执行计划里哪些地方出现了全表扫描,或者扫描数据过大,尽可能通过合理优化索引保证执行计划每个步骤都可以基于索引执行,避免扫描过多的数据。