MySQL查询中MRR优化的应用场景与示例解析求助
MySQL 8.0中MRR(Multi Range Read)优化的应用详解
一、MRR的核心逻辑
MRR是InnoDB针对二级索引回表场景的优化:当查询通过二级索引获取主键后,原本会逐个用主键回表(产生随机磁盘IO),MRR会先收集所有需要的主键、排序,再批量回表,把随机IO转化为顺序IO,减少磁盘寻道开销。
二、MRR的触发条件
MRR不是无条件启用的,MySQL优化器会结合场景自动判断,满足以下所有前提才会触发:
- 查询依赖二级索引(主键索引本身是有序聚簇索引,无需MRR)
- 查询需要回表(即SELECT的字段不全在二级索引中,必须访问聚簇索引获取数据)
- 会话/全局参数
optimizer_switch中mrr=on(MySQL 8.0默认开启) - 优化器评估后认为:收集排序主键的额外开销,小于直接随机回表的IO成本
三、MRR应用的实际示例
示例1:MRR生效并提升性能的场景
假设存在订单表:
CREATE TABLE `orders` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `order_time` DATETIME NOT NULL, `total_amount` DECIMAL(10,2) NOT NULL, INDEX idx_user_order_time (`user_id`, `order_time`) ) ENGINE=InnoDB;
执行查询:
SELECT id, user_id, order_time, total_amount FROM orders WHERE user_id = 1001 AND order_time BETWEEN '2024-01-01' AND '2024-01-31';
- 执行流程:
- 通过二级索引
idx_user_order_time筛选出符合条件的记录,得到一批主键id(这些id是按user_id+order_time排序的,并非主键本身的顺序) - 启用MRR时:先将所有
id收集、排序,再按排序后的id批量回表读取total_amount——此时回表是顺序IO(InnoDB聚簇索引按主键顺序存储),大幅减少磁盘寻道时间 - 不启用MRR时:直接用每个
id逐个回表,产生大量随机IO,性能较差
- 通过二级索引
示例2:MRR不生效或生效后变慢的场景
还是用上述订单表,执行查询:
SELECT id, user_id, order_time FROM orders WHERE user_id = 1001 AND order_time BETWEEN '2024-01-01' AND '2024-01-31';
- 此查询使用覆盖索引,所有需要的字段都在
idx_user_order_time中,无需回表,MRR不会被触发。
另一种场景:如果查询仅返回1-2条记录,优化器会判断收集排序主键的开销高于直接回表,此时即使满足触发条件,也会跳过MRR,强行启用反而会变慢。
四、为什么同一查询有时用MRR快、有时慢?
你遇到的波动主要来自以下几个因素:
- 结果集大小:结果集越大,顺序IO的优势越明显,MRR提速效果越强;结果集极小时,排序的额外开销会抵消IO收益,导致变慢
- 数据缓存:如果回表需要的数据已经在Buffer Pool中,直接回表命中缓存的速度远快于MRR的收集排序流程;如果数据不在缓存,MRR的顺序IO优势会凸显
- 表碎片化:若表经过大量删除、更新操作,聚簇索引出现碎片化,即使主键排序,回表仍会产生随机IO,MRR的优化效果消失
- 统计信息过时:MySQL优化器依赖表统计信息评估成本,如果统计信息过时,可能错误判断MRR的收益,导致选择了不合适的执行计划
五、如何调整MRR的使用
- 强制控制单查询:用查询提示强制启用或禁用MRR,比如:
-- 强制启用MRR SELECT /*+ MRR(orders) */ id, user_id, total_amount FROM orders WHERE ...; -- 强制禁用MRR SELECT /*+ NO_MRR(orders) */ id, user_id, total_amount FROM orders WHERE ...; - 调整参数:会话级或全局级修改
optimizer_switch,比如关闭MRR:SET SESSION optimizer_switch='mrr=off'; - 更新统计信息:执行
ANALYZE TABLE orders;,让优化器获得准确的表数据分布,做出更合理的判断
内容的提问来源于stack exchange,提问作者logavanan logi
相关产品推荐
相关产品推荐

