前言
MRR全称:Multi-Range Read,中文翻译过来就是多范围读取,主要解决的是当二级索引取出索引值后再去聚集索引中取行可能会造成大量的磁盘随机IO的问题,下面我们来具体进行分析。
Index Nested-Loop Join
在了解MRR前,我们先看一下什么是Index Nested-Loop Join(简称NLJ),直接翻译过来嵌套循环连接,就和我们平时写嵌套循环的思路是一样的,外层循环一个一个的去数,然后与内层循环挨个匹配。
假设要使用以下连接类型执行三个表t1、t2和 之间 t3的连接:
Table Join Type
t1 range
t2 ref
t3 ALL
如果使用简单的 NLJ 算法,则连接的处理方式如下:
for each row in t1 matching range {
for each row in t2 matching reference key {
for each row in t3 {
if row satisfies join conditions, send to client
}
}
}
Index Nested-Loop Join的问题
可以看出由于 NLJ 算法一次将一行从外循环传递到内循环,因此它通常会多次读取在内循环中处理的表。并且如果内循环中处理的表没有索引,那将会更加糟糕,所以又出现了Block Nested-Loop Join(简称BNL)优化,关于BNL在MySQL中的Block Nested Loop优化分析这篇文章中有具体分析。
NLJ、BNL共性问题
那无论是NLJ还是BNL都会存在一个问题,就是在匹配内循环表时,是按照外循环表传入的值来匹配,那很明显这样就会存在磁盘随机IO的问题。
磁盘随机IO案例
CREATE TABLE `t3` (
`id` int(11) NOT NULL,
`a` int(11) NULL DEFAULT NULL,
`b` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `a`(`a`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
插入一批数据,id不断递增到1000,a从1000递减到1
执行计划分析
EXPLAIN select * from t3 where a > 100 and a < 500;
正常情况下,执行
select * from t3 where a > 100 and a < 500;
会根据索引a以此取出101,然后回表到聚集索引中查询,对应的索引id为900,很明显这是一次磁盘随机IO的读取,那么完成a > 100 and a < 500范围的搜索,就会回表查询产生499次磁盘随机IO。
MRR优化
MRR优化的目的就是减少磁盘随机IO的产生,其查询优化过程大致如下:
1、先把通过二级索引取出的值缓存在缓冲区中。
2、再把这部分缓冲区中的数据按照ID进行排序。
3、然后再依次根据ID去聚集索引中获取整个数据行。
可以看出,只需要通过一次排序,就使得随机IO,变为顺序IO,使得数据访问更加高效。
read_rnd_buffer_size控制了数据能放入缓冲区的大小,如果一次性不够放就会分多次完成。