面向面试编程:MySQL的执行计划(二)

127 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

面试官:单表查询的执行计划你看过吗?

单表查询执行计划

假设写一个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语句是如何执行的?

多表关联查询

我们往往从驱动表里查出来一波数据之后,要对每一条数据都循环一次去被驱动表里查询数据,所以万一要是被驱动表的索引都没建好,总不能每次都全表扫描吧?另外一个,刚开始对驱动表根据WHERE条件进行查询的时候,也总不能全表扫描吧?这也是一个问题!

所以说,为什么有的时候多表关联很慢呢?答案就在这里了,两个表关联,先从驱动表里根据WHERE条件去筛选一波数据,这个过程如果没给驱动表加索引,万一走一个all全表扫描,岂不是速度很慢?其次,假设好不容易从驱动表里扫出来一波数据,接着又来一个for循环一条一条去被驱动表里根据ON连接条件和WHERE筛选条件去查,万一对被驱动表又没加索引,难道又来几十次或者几百次全表扫描?那速度岂不是慢的跟蜗牛一样了!

所以说,通常而言,针对多表查询的语句,我们要尽量给两个表都加上索引,索引要确保从驱动表里查询也是通过索引去查找,接着对被驱动表查询也通过索引去查找。如果能做到这一点,多表关联语句性能就会很高!