
什么是 MRR,它在什么情况下真正起作用
MRR(Multi-Range Read)不是“开了就快”的开关,而是 MySQL 在特定扫描模式下自动启用的优化策略。它只对 WHERE 条件中含范围(如 BETWEEN、、<code>>)且使用二级索引(非主键索引)的查询生效,并且必须满足:回表成本高(即二级索引查出大量主键 ID 后,随机读聚簇索引代价大)。
典型触发场景:SELECT * FROM t WHERE c2 BETWEEN 100 AND 200,其中 c2 是普通索引,而表数据量大、主键分布离散。
注意:MRR 不会用于全表扫描、主键范围扫描(如 PRIMARY KEY BETWEEN ...),也不适用于 ORDER BY 强制走索引排序的语句(此时 MySQL 会放弃 MRR 以保序)。
如何确认当前查询是否用了 MRR
最直接的方式是看 EXPLAIN FORMAT=JSON 输出中的 using_mrr 字段,或执行时开启 optimizer trace:
SET optimizer_trace="enabled=on"; SELECT * FROM t WHERE c2 BETWEEN 100 AND 200; SELECT * FROM information_schema.optimizer_trace\G
在 trace 中搜索 "mrr_costing" 或 "mrr_key_parts" 可确认是否进入 MRR 流程。
常见误判点:
- 看到
Extra: Using index condition≠ 用了 MRR;那是 ICP(Index Condition Pushdown) -
Extra: Using where; Using index表示覆盖索引,根本不需要回表,MRR 压根不参与 - 即使
optimizer_switch中mrr=on,MySQL 仍可能因估算成本高而跳过 MRR
mrr 和 mrr_cost_threshold 怎么调才有效
MRR 是否启用由两个系统变量协同控制:
-
mrr:全局开关,值为on或off(默认on) -
mrr_cost_threshold:浮点数,默认10.0,表示“启用 MRR 的收益阈值”。MySQL 会对比传统回表 vs MRR 回表的成本估算,仅当后者节省超该阈值时才用
调参建议:
- 不要盲目设
mrr_cost_threshold=0—— 这会让小范围查询也强制走 MRR,反而因排序+缓冲区开销变慢 - 若观察到大量中等范围查询(比如每次返回 500~2000 行)未启用 MRR,可尝试小幅下调阈值(如
5.0),再结合EXPLAIN验证 -
mrr关闭后,mrr_cost_threshold失效;但反过来,mrr=on不代表一定启用——成本模型才是最终裁判
MRR 实际流程里最容易被忽略的瓶颈
MRR 的核心是“先取一批主键 → 排序 → 批量读取聚簇索引”,这个过程依赖 read_rnd_buffer_size 缓冲区大小:
- 该值太小(默认 256KB),会导致一批次只攒几十个主键就 flush,失去批量优势
- 该值太大(如设为 4MB),可能引发单次内存分配失败,或在高并发下耗尽线程内存
- 真实效果取决于数据物理分布:若主键本身局部性好(如自增 ID + 范围条件较窄),MRR 收益极低,甚至不如原方式
所以别只盯着 mrr 开关——read_rnd_buffer_size 必须和你的典型查询返回行数、主键离散度一起看。线上调优前,先用 sys.schema_table_statistics_with_buffer 查看实际 rnd_next 次数变化。










