MRR(Multi-Range Read)不减少回表次数,而是将无序主键回表转化为有序批量读取,从而把随机I/O变为顺序I/O,显著降低磁盘寻道开销;其生效需满足二级索引范围查询+回表条件,且EXPLAIN中显示Using MRR。

什么是MRR,它真能减少回表次数?
MRR(Multi-Range Read)本身不减少回表次数,而是减少回表过程中的随机 I/O 次数。回表动作依然发生——MySQL 仍需根据二级索引查出的主键去聚簇索引取整行数据;但 MRR 把原本杂乱无序的主键访问,先缓存、排序,再按物理顺序批量读取,把多次随机磁盘寻址变成更高效的顺序读。
典型触发场景是 WHERE 含范围条件(如 c1 BETWEEN 10 AND 1000)、IN 列表查询或某些 JOIN 回表。若执行计划中 Extra 出现 Using MRR,说明该优化已生效。
如何确认MRR是否启用并生效?
直接查 SHOW VARIABLES LIKE 'optimizer_switch';,确认其中 mrr=on 且 mrr_cost_based=on(后者表示优化器按成本决策是否启用 MRR,推荐保持开启)。
执行 EXPLAIN SELECT ... 观察两处关键信息:
-
type为range、ref或eq_ref时才可能触发 MRR -
Extra字段含Using MRR才代表本次查询实际启用了 MRR - 若只有
Using index condition而无Using MRR,说明索引下推(ICP)起了作用,但 MRR 未参与
注意:mrr=on 是全局开关,但单条 SQL 是否启用仍由优化器基于成本估算决定——比如当预估回表行数极少(
MRR依赖哪些配置和前提条件?
MRR 不是“开了就加速”,它需要底层配合才能发挥效果:
- MySQL 版本 ≥ 5.6(InnoDB 引擎),且表使用
ROW_FORMAT=DYNAMIC或COMPACT -
read_rnd_buffer_size决定 MRR 缓冲区大小,默认 256KB;太小会导致分批过多,太大可能挤占其他线程内存;建议按单行主键+辅助索引字段大小 × 预估回表行数来估算,一般调至 512KB–2MB - 被扫描的二级索引必须包含足够选择性的字段;若
WHERE条件匹配 80% 的行,优化器大概率放弃 MRR,转而走全表扫描 - 仅对 InnoDB 生效;MyISAM 不支持 MRR
执行 SELECT @@read_rnd_buffer_size; 可查看当前值,线上调整建议用 SET SESSION read_rnd_buffer_size = 1048576; 先测试,避免影响全局。
MRR和覆盖索引,到底该优先选哪个?
覆盖索引是消除回表,MRR 是优化回表过程——二者不在同一层级,也不互斥。真实优化路径应是:
- 第一步:优先设计覆盖索引,比如把
SELECT id, name, status FROM t WHERE c1 > 100改成联合索引(c1, id, name, status),直接让Extra显示Using index - 第二步:若无法覆盖(如查询
TEXT字段、或字段组合太多),再看是否满足 MRR 条件;此时开启 MRR + 调大read_rnd_buffer_size才有意义 - 第三步:若 MRR 仍不生效,检查是否因统计信息陈旧导致优化器误判——运行
ANALYZE TABLE t;更新索引基数
最容易被忽略的一点:MRR 对 SSD 收益远小于机械盘。在 NVMe 环境下,随机 I/O 延迟已大幅降低,MRR 的排序+顺序读开销反而可能成为负优化。上线前务必在同类型存储上压测验证。











