乱序主键ID触发的回表强制产生随机I/O,是性能瓶颈根源;MRR通过排序ID实现批量顺序回表,Using MRR是其启用标志,配合ICP和合理read_rnd_buffer_size可显著优化。

回表本身不产生随机I/O,但**乱序主键ID触发的回表会强制产生随机I/O**——这是性能瓶颈的根源。MRR、覆盖索引等手段本质都是在干预这个“ID序列是否有序”这件事。
看执行计划里有没有Using MRR
这是判断MySQL是否启用多范围读的最直接信号。没有它,说明回表走的是传统逐条模式,ID完全按二级索引顺序(比如score值升序)取出,而这些ID在聚簇索引中大概率是跳跃分布的。
-
Using MRR出现,代表MySQL已收集一批ID、排序后再批量回表,磁盘访问趋于连续 - 若同时看到
Using index condition,说明ICP也在生效,进一步减少了无效ID的生成 - 如果
Extra列是空的或只有Using where,基本确认发生了未优化的随机回表
检查read_rnd_buffer_size是否太小
MRR依赖内存缓冲区暂存ID并排序,缓冲区太小会导致频繁刷写和分批处理,削弱顺序性优势。
- 默认值通常为256KB,对百万级结果集明显不够;建议根据典型查询返回行数预估:每行主键约8字节,10万ID需约800KB
- 临时调大可验证效果:
SET SESSION read_rnd_buffer_size = 4194304;(4MB) - 注意该变量是会话级,且不能超过
max_read_rnd_buffer_size上限
用ORDER BY primary_key强制ID有序
当无法开启MRR或MRR被成本模型拒绝时,可以手动让ID变得有序,把随机I/O压力转嫁给排序阶段。
- 例如原查询:
SELECT * FROM student WHERE score BETWEEN 0 AND 59; - 改写为:
SELECT * FROM student WHERE score BETWEEN 0 AND 59 ORDER BY id LIMIT 1000; - 此时优化器可能选择先走
score索引拿到ID,再按id排序,最后顺序回表——效果接近MRR,且不依赖开关 - 缺点:
ORDER BY本身有CPU开销,数据量极大时慎用
为什么mrr_cost_based=off经常要关?
MySQL默认用成本估算决定是否启用MRR,但它的模型常低估随机I/O的真实代价,尤其在SSD或高并发场景下容易误判。
- 开启方式:
SET optimizer_switch='mrr=on,mrr_cost_based=off'; - 只影响当前会话,生产环境建议在SQL Hint中控制:
SELECT /*+ USE_MRR(t) */ * FROM student t WHERE ... - 注意:如果二级索引本身就包含主键(如联合索引
(a, id)),MRR不会生效——因为ID已天然有序
真正难处理的是那些“看起来能走索引,实际回表ID极度离散”的查询,比如用UUID主键 + 高频WHERE status = ?,这时MRR收益有限,覆盖索引又难设计——得回到主键本身去动刀。











