explain未显示using mrr不代表mrr未生效,因其由优化器按成本估算决定;需通过explain format=json查"using_mrr": true、extra列组合、rnd_next/rnd_pos变化等综合验证。

EXPLAIN里没出现Using MRR,不代表MRR没生效
很多人一查EXPLAIN没看到Using MRR就断定优化没起作用,这是最常见的误判。MRR是否启用由优化器按成本估算决定,不是“开了开关就一定显示”。即使optimizer_switch中mrr=on且mrr_cost_based=on(默认),只要优化器算出来传统回表更便宜,它就会跳过MRR。
真正可靠的验证方式是组合判断:
- 执行
EXPLAIN FORMAT=JSON,检查输出中是否存在"using_mrr": true字段 - 观察
Extra列:若同时出现Using index condition和Using MRR,说明ICP与MRR协同工作;只有前者而无后者,大概率是read_rnd_buffer_size太小或预估回表行数低于阈值(默认约10行) - 查
sys.schema_table_statistics,对比同一查询下rnd_next(随机读次数)和rnd_pos(顺序读次数)的变化——MRR生效时,rnd_next应明显下降,rnd_pos上升
哪些WHERE条件能真正触发MRR
MRR只在特定扫描路径下被考虑,不是所有带范围的SQL都能用上。核心三要素缺一不可:走二级索引 + 必须回表 + 主键物理分布离散。
有效场景示例:
-
WHERE status IN (1,2,3)(二级索引+回表+主键乱序) -
WHERE created_at BETWEEN '2025-01-01' AND '2025-12-31'(联合索引最左匹配后带范围,且SELECT * 或含非索引列) -
WHERE category = 'A' AND price > 100(联合索引(category, price),price为范围,需回表取其他字段)
无效场景:
-
SELECT id, name FROM t WHERE status = 1(覆盖索引,根本无需回表) -
SELECT * FROM t WHERE id > 1000(主键扫描,不经过二级索引) -
WHERE JSON_CONTAINS(data, '"abc"')(虚拟列索引不支持MRR)
read_rnd_buffer_size调多大才合适
这个会话级参数直接影响MRR能否启动以及效果好坏,但它不是越大越好。
默认256KB太小,常导致小中等结果集直接跳过MRR;设到16MB又可能引发高并发OOM,尤其当二级索引本身很宽(比如含TEXT字段)时,排序开销反而压倒IO节省。
实操建议:
- 先用
SET SESSION read_rnd_buffer_size = 4194304(4MB)测试,确保在同一session里跑EXPLAIN或真实查询 - 结合
sys.schema_table_statistics看rnd_next是否显著下降,而不是只盯执行时间 - 若发现大量500–2000行的查询仍没启用MRR,可尝试下调
mrr_cost_threshold(如设为5.0),但不要设为0——小范围查询强制MRR反而更慢
MRR和覆盖索引到底谁优先
这不是选择题,而是优化层级问题:覆盖索引是消除回表,MRR是优化回表过程。两者不互斥,但有明确先后顺序。
真实优化路径必须是:
- 第一步:优先设计覆盖索引。例如把
SELECT id, name, status FROM t WHERE c1 > 100改成联合索引(c1, id, name, status),让EXPLAIN显示Using index - 第二步:若无法覆盖(如要查
TEXT、BLOB字段,或字段组合太多导致索引过大),再确认是否满足MRR前提,并调整read_rnd_buffer_size和成本阈值
最容易被忽略的一点是:MRR对InnoDB生效,MyISAM完全不支持;且仅当表使用ROW_FORMAT=DYNAMIC或COMPACT时才起效——REDUNDANT或COMPRESSED格式下MRR会被静默忽略。











