MRR 是“按主键排序后批量回表”,即先收集二级索引查出的主键值、排序、再顺序回表,将随机读转为顺序读;它不并行扫描多范围,仅对二级索引回表生效,覆盖索引无效。

什么是 MRR:不是“多范围读取”,而是“按主键排序后批量回表”
MRR(Multi-Range Read)本质不是让 MySQL 同时读多个索引范围,而是把原本分散的、基于二级索引查出的 rowid(通常是主键值),先收集起来,排序,再按主键顺序批量回表。这把随机的主键访问,转成了相对连续的磁盘读——关键在“排序后再读”,不在“多范围”。
常见误解是以为 MRR 能并行扫多个索引区间,其实它只对单个 WHERE 中的多个等值条件(如 col IN (1,5,9,12))或范围(如 col BETWEEN 10 AND 100)生效,且必须走二级索引 + 回主键表才可能触发。
- MRR 默认关闭,需设置
optimizer_switch='mrr=on,mrr_cost_based=on' - 仅当优化器估算回表成本高于排序+顺序读时才启用,
mrr_cost_based=off可强制用,但未必更快 - 对覆盖索引(
SELECT字段全在索引里)无效——根本不需要回表,MRR 不介入
怎么确认 MRR 是否真起了作用
不能只看 EXPLAIN 输出里有没有 Using MRR,因为它是“计划阶段标记”,不代表实际执行时被采纳。真正要看的是 STATUS 计数器和 OPTIMIZER_TRACE。
- 执行前清空状态:
FLUSH STATUS;执行后查SHOW STATUS LIKE 'Handler_read%',若Handler_read_next显著下降、Handler_read_rnd_next减少,说明随机回表减少了 - 开启
SET optimizer_trace="enabled=on",执行 SQL 后查information_schema.OPTIMIZER_TRACE,搜索"mrr_cost"和"use_mrr"字段,看是否为true -
EXPLAIN FORMAT=JSON中找"using_mrr": true和"rows_estimation"里是否包含"mrr_cost"项
MRR 在 SSD 和 HDD 上效果差异极大
SSD 对随机读延迟不敏感,MRR 带来的收益常被排序开销抵消;而 HDD 上顺序读吞吐可比随机读高 5–10 倍,MRR 很容易带来 2x+ 性能提升。但这个差异不是由存储引擎决定的,而是由 MySQL 的 read_rnd_buffer_size 和底层 I/O 模式共同影响。
-
read_rnd_buffer_size默认 256KB,太小会导致分批太多,排序/合并开销上升;太大又浪费内存,尤其并发高时 - HDD 场景建议调到 1MB–4MB,SSD 场景保持默认或略降(如 128KB),避免无谓排序
- 如果表使用
COMPRESSED行格式,MRR 排序后仍要解压页,可能反而增加 CPU 开销,需实测
为什么有时候开了 MRR 反而更慢
最常见原因是回表数据量小,或者主键本身高度有序(比如自增 ID + 时间范围查询),此时排序没意义,还多了一次内存拷贝和 qsort 调用。
- 当
EXPLAIN显示rowskey_len 较大(二级索引宽),MRR 很可能拖慢查询 - 联合索引中主键列位置靠后(如
(a,b,primary_key)),MySQL 可能无法高效提取primary_key值,导致 MRR 构建失败或退化 -
ORDER BY primary_key查询本就天然顺序,MRR 不仅无益,还可能干扰优化器选择index_merge等更优路径
复杂点在于:MRR 是否启用,取决于优化器对“排序成本 vs 随机读成本”的动态估算,而这个估算依赖统计信息准确度。表 ANALYZE 不及时、直方图缺失,都可能导致误判。

















