MRR,全称是“Multi-RangeReadOptimization”。简单来说:MRR通过将“随机磁盘读取”转化为“顺序磁盘读取”来提高索引查询的性能。至于:为什么我们需要将随机读取转换为顺序读取?如何转换它们?为什么顺序读可以提高读性能?让我们开始吧。磁盘:底层辛苦的人进行范围查询:mysql>explainselect*fromstuwhereagebetween10and20;+----+------------+--------+-------+------+----------+------+------+----------------------+|id|select_type|table|type|key|key_len|ref|rows|Extra|+----+------------+-------+--------+----------------+------+------+----------------------+|1|SIMPLE|stu|range|age|5|NULL|960|Usingindexcondition|+----+-----------+--------+--------+----------------+------+------+------------当这条sql执行时,MySQL会去磁盘读取数据(假设数据不在数据缓冲池中)如图下图:图红线是整个查询过程,蓝线是磁盘的移动路线。这张图是按照Myisam的索引结构画的,但是同样适用于Innodb。对于Myisam,左边是字段age的二级索引,右边是存放完整行数据的地方。先找左边的二级索引,找到满足条件的第一条记录(其实每个节点就是一个页??面,一个页面可以有很多条记录,这里我们假设每个页面只有一条记录),然后向右阅读获取此数据的完整记录。读完后向左返回,继续寻找下一条满足条件的记录。找到后,在右侧阅读。这时候你发现这条数据跟上一条数据是一致的。就物理存储位置而言,它是很远的!没办法,唯一的办法就是让磁盘和磁头一起做机械运动,给你读取这个数据。第三个和第四个是一样的。每次读取数据时,磁盘和磁头都要走很远的路。磁盘的简化结构可以看成下面这样:想象一下,为了执行你的sql语句,磁盘必须不停地转动,磁头必须不停地移动。这些机械运动非常耗时。10,000RPM(RevolutionsPerMinute,即每分钟转数)机械硬盘每秒大约可以进行167次磁盘读取,所以在极端情况下,MySQL每秒只能给你返回167条数据,这还不算CPU排队时间.以上都是机械硬盘和SSD的土豪,请放心——Innodb也一样。Innodb是聚簇索引(clusterindex),所以只需要将右边换成叶子节点上有完整数据的B+树即可。顺序阅读:一场风雨兼程的革命到这里你就知道磁盘随机访问是多么奢侈了,所以,显然,随机访问应该转化为顺序访问:mysql>setoptimizer_switch='mrr=on';QueryOK,0rowsaffected(0.06sec)mysql>explainselect*fromstuwhereagebetween10and20;+----+------------+--------+--------+------+---------+-----+-----+-------------+|id|select_type|table|type|key|key_len|ref|rows|Extra|+----+------------+--------+--------+------+-------+------+------+----------------+|1|SIMPLE|tbl|range|age|5|NULL|960|...;UsingMRR|+----+------------+--------+------+------+--------+------+------+---------------+我们启用MRR,重新执行sql语句,发现Extra里多了一个“UsingMRR”。现在MySQL的查询过程会变成这样:对于Myisam来说,在去磁盘获取完整数据之前,会根据rowid进行排序,然后依次读取磁盘。对于Innodb来说,会根据聚簇索引键值进行排序,然后顺序读取聚簇索引。顺序读取有几个好处:1.磁盘和磁头不再需要来回做机械运动;2.可以充分利用磁盘预读。比如客户端请求一页数据的时候,后面几页的数据也可以一起包含进来。返回并放入数据缓冲池中,这样下次如果需要下一页的数据就不用再从磁盘中读取了。其理论依据是计算机科学中众所周知的局部性原则:当使用一条数据时,通常会立即使用附近的数据。3.在一次查询中,每一页的数据只会从磁盘中读取一次。MySQL从磁盘中读取该页的数据后,会将数据放入数据缓冲池中。如果下次使用这个页面,就不需要去磁盘读取了,直接从内存中读取。但是如果不排序的话,可能你读完第1页的数据之后,你会去读第2、3、4页的数据,然后你再去读第1页的数据,这时候你发现那个页1该页的数据已经从缓存中移除,所以必须重新从磁盘读取第一页的数据。转为顺序读后,你会继续使用第1页的数据,此时根据MySQL的缓存清除机制,直到你用完本页的数据,本页的缓存才会失效,因为是Sequentialreading,在这个query剩下的过程中,你确定这个页面的数据不会被再次使用,所以你可以和这个页面的数据说再见了。顺序阅读通过这三个方面最大程度的优化了索引阅读。别忘了索引本身就是为了减少磁盘IO,加快查询速度,而MRR则是进一步放大索引减少磁盘IO的作用。这个革命和MRR相关的配置有两个:mrr:on/offmrr_cost_based:on/off第一个是上面演示中用来开启MRR的开关:mysql>setoptimizer_switch='mrr=on';如果不开启,MRR肯定用不上。另一种是根据使用MRR的成本来告诉优化器是否使用MRR,考虑是否值得使用MRR(cost-basedchoice),在具体的SQL语句中决定是否使用MRR。很明显,对于只返回一行数据的查询来说,MRR并不是必须的,而且如果你将mrr_cost_based设置为off,优化器将完全使用MRR,这在某些情况下是非常愚蠢的,所以建议这个配置仍然设置为毕竟,优化器在大多数情况下是正确的。还有一个配置read_rnd_buffer_size,用来设置用来排序rowid的内存大小。显然,MRR本质上是一种以空间换取时间的算法。MySQL不可能给你无限的内存来排序。如果read_rnd_buffer满了,它会先把满的rowids排序,从磁盘中读取出来,然后清空,然后继续往里放rowids,直到read_rnd_buffer再次达到read_rnd_buffer的配置。上限等。最后可以看到,MRR跟索引有很大的关系。索引是MySQL对查询所做的优化。它将原本杂乱无章的数据组织成有序的结构,使全表扫描成为基于规则的查询。我们说的MRR是MySQL针对索引查询的一种优化,可以说是优化中的优化。优化MySQL查询,首先要了解MySQL查询流程;而要优化索引查询,就必须了解MySQL索引的原理。正如我在《如何学习MySQL》中所说,要优化一项技术,学会调优,首先要了解它的原理。两者是不同层次的。以上(这里应该有点赞)。
