MySQL5.6版本开始支持MRR优化,目的是为了减少磁盘访问,并将磁盘随机访问转化为较为顺序的数据访问。这对IO-bound型的SQL查询语句可带来性能极大的提升。MRR优化可适用于range ref eq_ref类型的查询
IO bound:如果主要时间消耗在等待读写数据而CPU利用率很低,那么该任务就是I/O-bound,I/O束缚型。
先来一个范围查询
1 | mysql > explain select * from stu where age between 10 and 20; |
图中的红线就是整个查询过程,蓝色线是磁盘的运动路线
左边是字段age的二级索引,右边是存储完整行记录的地方(回表操作)。
先到左边的二级索引找,找到第一条符合条件的记录,接着到右边去读取这条数据的完整记录,读取完后回到左边继续找下一条符合条件的记录,然后到右边读取,这时发现这条数据和上条数据在物理存储位置上离得非常远!
为了读取记录只能让磁盘和磁头一起运动去读取这条记录。
第三条,第四条,都是一样,每次读取数据,磁盘和磁头都要跑好远的路。
MySQL是以页为单位读取数据的,这里假设这几条数据都恰好位于不同的页上,另外,页的思想来源于操作系统的非连续内存管理机制,类似的还有段。
开启MRR重新执行SQL语句
1 | mysql > set optimizer_switch='mrr=on'; |
对于innodb,则会按照聚簇索引键值排好序,再顺序的读取聚簇索引。
顺序读带来的好处
- 磁盘和磁头不再来回的做机械运动
- 可以充分使用磁盘预读。
比如客户端请求一页的数据时,可以吧后面几页的数据也一起返回,放到数据缓冲池中,这样如果下次刚好需要下一页的数据,就不需要到磁盘读取,这样做的理论依据是计算机科学中著名的局部性原理
当一个数据被用到时,其附近的数据也通常会马上被使用。
- 在一次查询中,每一页的数据只会从磁盘读取一次
MySQL 从磁盘读取页的数据后,会把数据放到数据缓冲池,下次如果还用到这个页,就不需要去磁盘读取,直接从内存读。
但是如果不排序,可能你在读取了第 1 页的数据后,会去读取第2、3、4页数据,接着你又要去读取第 1 页的数据,这时你发现第 1 页的数据,已经从缓存中被剔除了,于是又得再去磁盘读取第 1 页的数据。
而转化为顺序读后,你会连续的使用第 1 页的数据,这时候按照 MySQL 的缓存剔除机制,这一页的缓存是不会失效的,直到你利用完这一页的数据,由于是顺序读,在这次查询的余下过程中,你确信不会再用到这一页的数据,可以和这一页数据说告辞了。
索引本身就是为了减少磁盘IO,加快查询,MRR则是把索引减少磁盘IO的作用进一步放大。