











MRR (Multi-Range Read,多范围读) 是MySQL 5.7 默认启用的一个优化策略。
Server 层执行器要数据,得通过存储引擎 API。在没有 MRR 的时候,执行器拿回表数据的方式是一次要一条:
没有 MRR 的调用过程:
Server: 引擎,把主键=12 的行给我
InnoDB: (去聚集索引读一次 → 返回)
Server: 引擎,把主键=58 的行给我
InnoDB: (再读一次 → 返回)
Server: 引擎,把主键=3291 的行给我
InnoDB: (再读一次 → 返回)
...重复 500 次...
每次调用引擎只能看到"一条主键",
它没法提前知道下一个要读什么 → 无法规划 → 只能来一条读一条(乱序随机)
有了 MRR,API 变成了一次给一批:
有 MRR 的调用过程:
Server: 引擎,主键在 [12, 58, 3291, 8812, ...] 这个"多范围"集合里的行,一次都给我
InnoDB: (看到完整批次了!)
→ 我先排序 → 12, 58, 3291, 8812
→ 我再按页合并 → P1(12,58), P6(3291), P34(8812)
→ 按页顺序批量读 → 一起返回
即:
没有 MRR:
通过二级索引找到多个主键值 → 按主键值出现的顺序随机回表 I/O
有 MRR:
通过二级索引找到多个主键值 → 先排序主键 → 按主键顺序批量回表
→ 磁盘随机 I/O 变顺序 I/O → 更快
MRR 带来的收益是真实的,收益来自三个方面:
单调递增的页访问 ≠ 乱序跳转
聚集索引的叶子页在磁盘上按主键顺序排列(B+Tree 的本质):
页P1 页P2 页P3 ... 页P20
[id=1~100][id=101~200][id=201~300] [id=1901~2000]
↑
PK=12, 580, 3291, 8812 分别落在 P1, P6, P34, P89
没有 MRR(按二级索引扫出来的顺序回表):
扫二级索引得到:8812, 12, 3291, 580, ...
回表顺序:P89 → P1 → P34 → P6 → ...
磁盘磁头:跳到89 → 跳回1 → 跳到34 → 跳回6
HDD 每次跨页寻道 5-10ms,来回横跳
有 MRR(先排序再回表):
排序后:12, 580, 3291, 8812
回表顺序:P1 → P6 → P34 → P89
磁头朝一个方向走,每次只跨几个磁道
→ 不是严格连续,但接近顺序扫的代价
HDD 上"单调前进"和"来回横跳"的差距就是好几倍,这跟电梯类比:从 1 楼 → 6 楼 → 34 楼 → 89 楼往上走,比 89 → 1 → 34 → 6 来回折返快得多。
同一页内的多条主键,合并成一次读取
这是 MRR 收益最大的一块:
二级索引扫出 500 个主键,其中 30 个落在 P1(id=1~100)
没有 MRR:
回表读 id=12 时读一次 P1
过一会回表读 id=58 时,P1 可能已被挤出 Buffer Pool → 再读一次 P1
最坏情况:同一个页被读了 30 次
有 MRR:
排序后,P1 里的 30 个主键集中处理
读一次 P1,把 30 行全取出来
→ 500 次回表可能压缩成 80 次页读取
预读(read-ahead)友好
磁盘/OS 层都有预读机制:你读了 P6,它顺手把 P7、P8 也捞进缓存。乱序访问时预读的页用不上就浪费了;单调递增访问时预读的页大概率马上用到。
▎ MRR 把"乱序回表"变成"按主键单调递增的页访问",虽然不是物理连续的顺序 I/O,但实现了:
▎ ① 磁头单向移动(减少寻道) ② 同页主键合并读取(减少重复读页) ③ 预读友好。
▎
▎ 本质是降低随机性,而不是达到严格顺序。
另外 :MySQL 5.7 里 MRR 默认开着,但 mrr_cost_based=on 意味着优化器会按成本决定用不用;在 HDD 上收益明显,在 SSD 上收益小但仍有效(因为减少了 I/O 请求次数)。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。