MRR通过预收集并排序rowid再顺序回表,将随机读转化为局部顺序读;需满足多行匹配、足够read_rnd_buffer_size及主键物理连续等条件才能生效。

什么是MRR,它怎么把随机读变顺序读
MRR(Multi-Range Read)不是“加速索引扫描”的功能,而是MySQL在执行WHERE条件匹配多个二级索引键值后,对回表操作做的预处理优化。它的核心动作是:先收集一批rowid(或主键值),排序后再按物理顺序批量访问聚簇索引——这把原本散落在磁盘各处的随机I/O,转化成局部连续的顺序读。
常见错误现象:EXPLAIN里看到Using MRR,但Handler_read_rnd_next没降反升;或者innodb_random_read_ahead被误开,反而干扰MRR生效。
- MRR只对
range、ref、eq_ref等能生成多行rowid的访问方式起作用,单行SELECT ... WHERE pk = ?不会触发 - 必须开启
read_rnd_buffer_size(默认256KB),且该缓冲区要能装下本轮所有rowid,否则退化为原始随机读 - InnoDB引擎下,MRR依赖
optimizer_switch='mrr=on,mrr_cost_based=on',后者控制是否基于代价启用;设为off会强制启用,但可能更慢
为什么开了MRR,磁盘IO还是高
根本原因常是MRR没真正生效,或生效了但数据分布/缓存状态让它“白忙”。典型场景:大范围range查询 + 低缓存命中率 + 主键高度分散。
使用场景判断:用SHOW STATUS LIKE 'Handler_read%'对比Handler_read_rnd和Handler_read_rnd_next,若前者下降但后者仍高,说明MRR在工作,但后续回表仍大量落盘。
-
read_rnd_buffer_size太小(比如设成8KB),导致每批只攒几个rowid就刷出,顺序性差,IO次数不减 - 表没建主键,或用
UUID作主键,rowid排序后物理位置依然跳跃,MRR收益极低 -
innodb_buffer_pool_size过小,即使顺序读,页也频繁进出内存,innodb_data_reads指标看不出改善
如何验证MRR是否在干活
不能只看EXPLAIN输出里的Using MRR,那只是计划阶段的标记;得看实际执行路径和IO行为。
实操建议:
- 执行
SELECT /*+ USE_INDEX_MERGE(t) */ * FROM t WHERE a IN (1,2,3,...)这类明显触发MRR的语句,再查SHOW PROFILE FOR QUERY N,关注Handler_read_rnd_next下降幅度 - 用
perf record -e block:block_rq_issue -p $(pidof mysqld)抓块设备请求,观察IO offset是否出现明显分段聚集(顺序读特征) - 临时关闭MRR:
SET SESSION optimizer_switch='mrr=off';,对比两次SELECT的Handler_read_rnd_next和innodb_data_reads,差值才是真实收益
哪些情况MRR反而更慢
MRR不是银弹。当数据局部性差、内存充足、或查询本身就很窄时,它可能引入额外排序开销,还挤占read_rnd_buffer_size资源。
性能影响关键点:
- 小结果集( 随机读延迟,
mrr_cost_based=on通常会自动禁用,但手动强制开就吃亏 - 高并发小查询场景:每个连接都分配
read_rnd_buffer_size,总内存消耗陡增,可能触发swap - SSD盘上效果弱于HDD:顺序读优势缩小,而CPU排序开销不变;某些NVMe场景甚至观测到QPS微跌
容易被忽略的是:MRR只优化回表路径,不改变二级索引扫描本身的随机性。如果WHERE条件导致二级索引页也大量跳跃(比如非前缀索引+范围查询),磁盘压力源头根本不在MRR能覆盖的环节。










