MySQL 优化器之Index merge Multi-Range Read MRR与Batched Key Access使用案例详解

Posted ShenLiang2025

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL 优化器之Index merge Multi-Range Read MRR与Batched Key Access使用案例详解相关的知识,希望对你有一定的参考价值。

mysql 优化器之Index merge Multi-Range Read  MRR与Batched Key Access介绍

索引合并Index merge

覆盖索引是在索引里即有查询时需要的字段,而在一个表上的查询往往条件会有多个组合且较为复杂,所以很难全部由覆盖索引包含到。那么此时我们就会用到索引合并(index merge)算法。

该算法主要包含三类情况:

算法

应用场景

执行计划

Intersection

and

Using intersect(...)

Union

or

Using or(...)

Sort-Union

带范围查询的or

sort_union(...)

交Intersection算法

EXPLAIN ANALYZE
SELECT *
FROM sakila.payment
WHERE staff_id = 1
AND customer_id = 75;
--结果
-> Filter: ((payment.customer_id = 75) and (payment.staff_id = 1))  (cost=8.12 rows=20) (actual time=2.329..2.637 rows=22 loops=1)

-> Index range scan on payment using intersect(idx_fk_customer_id,idx_fk_staff_id)  (cost=8.12 rows=20) (actual time=2.325..2.617 rows=22 loops=1)

从执行计划里我们不难看出这里用到了索引交(intersect)

而通过explain命令则可以看出是

mysql> EXPLAIN  SELECT * FROM sakila.payment WHERE staff_id = 1 AND customer_id = 75\\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: payment
   partitions: NULL
         type: index_merge
possible_keys: idx_fk_staff_id,idx_fk_customer_id
          key: idx_fk_customer_id,idx_fk_staff_id
      key_len: 2,1
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: Using intersect(idx_fk_customer_id,idx_fk_staff_id); Using where

通过观察该查询和表的结构可以看出

show create table sakila.payment
--结果
CREATE TABLE `payment` (
  `payment_id` smallint unsigned NOT NULL AUTO_INCREMENT,
  `customer_id` smallint unsigned NOT NULL,
  `staff_id` tinyint unsigned NOT NULL,
  `rental_id` int DEFAULT NULL,
  `amount` decimal(5,2) NOT NULL,
  `payment_date` datetime NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`payment_id`) USING BTREE,
  KEY `idx_fk_staff_id` (`staff_id`) USING BTREE,
  KEY `idx_fk_customer_id` (`customer_id`) USING BTREE,
  KEY `fk_payment_rental` (`rental_id`) USING BTREE,
  CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT `fk_payment_rental` FOREIGN KEY (`rental_id`) REFERENCES `rental` (`rental_id`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `fk_payment_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`staff_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16050 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

字段staff_id 和customer_id 有各自的索引idx_fk_staff_id、idx_fk_customer_id,而我们的查询是等值条件的交集。

同理如下查询也是应用到了index merge的intersection算法。

EXPLAIN
SELECT *
FROM sakila.payment
WHERE payment_id > 10
AND customer_id = 318;

并Union算法

当一个表查询中用到一系列的含OR的等值条件时一般会用到并算法。示例查询见下:

SELECT *
FROM sakila.payment
WHERE staff_id = 1
OR customer_id = 318;

SELECT *
FROM sakila.payment
WHERE payment_id > 15000
OR customer_id = 318;
-- 通过explain或者explain、EXPLAIN ANALYZE()
mysql> EXPLAIN SELECT * FROM sakila.payment WHERE staff_id = 1 OR customer_id = 318\\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: payment
   partitions: NULL
         type: index_merge
possible_keys: idx_fk_staff_id,idx_fk_customer_id
          key: idx_fk_staff_id,idx_fk_customer_id
      key_len: 1,2
          ref: NULL
         rows: 8069
     filtered: 100.00
        Extra: Using union(idx_fk_staff_id,idx_fk_customer_id); Using where

mysql> EXPLAIN ANALYZE SELECT * FROM sakila.payment WHERE staff_id = 1 OR customer_id = 318\\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: ((sakila.payment.staff_id = 1) or (sakila.payment.customer_id = 318))  (cost=2306.30 rows=8069) (actual time=0.224..103.595 rows=8062 loops=1)
-> Index range scan on payment using union(idx_fk_staff_id,idx_fk_customer_id)  (cost=2306.30 rows=8069) (actual time=0.221..98.988 rows=8062 loops=1)

mysql> EXPLAIN FORMAT=tree SELECT * FROM sakila.payment WHERE staff_id = 1 OR customer_id = 318\\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: ((sakila.payment.staff_id = 1) or (sakila.payment.customer_id = 318))  (cost=2116.85 rows=8069)
    -> Index range scan on payment using union(idx_fk_staff_id,idx_fk_customer_id)  (cost=2116.85 rows=8069)

排序并Sort Union算法

跟并算法类似,不过这里用的不是等值条件而是范围,详见如下例子:

-- 查询payment表里customer_id小于30或者rental_id小于10的信息。
mysql> EXPLAIN SELECT * FROM sakila.payment WHERE customer_id < 30 OR rental_id < 10\\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: payment
   partitions: NULL
         type: index_merge
possible_keys: idx_fk_customer_id,fk_payment_rental
          key: idx_fk_customer_id,fk_payment_rental
      key_len: 2,5
          ref: NULL
         rows: 826
     filtered: 100.00
        Extra: Using sort_union(idx_fk_customer_id,fk_payment_rental); Using where

查询payment表里customer_id小于20或者rental_id大于18000的信息。
mysql> EXPLAIN SELECT * FROM sakila.payment WHERE customer_id < 20 OR rental_id >18000\\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: payment
   partitions: NULL
         type: index_merge
possible_keys: idx_fk_customer_id,fk_payment_rental
          key: idx_fk_customer_id,fk_payment_rental
      key_len: 2,5
          ref: NULL
         rows: 514
     filtered: 100.00
        Extra: Using sort_union(idx_fk_customer_id,fk_payment_rental); Using where


EXPLAIN ANALYZE
SELECT /*+ NO_INDEX_MERGE(payment) */
*
FROM sakila.payment
WHERE staff_id = 1
AND customer_id = 75;

-> Filter: (payment.staff_id = 1)  (cost=12.30 rows=21) (actual time=0.151..0.179 rows=22 loops=1)
-> Index lookup on payment using idx_fk_customer_id (customer_id=75)  (cost=12.30 rows=41) (actual time=0.149..0.167 rows=41 loops=1)
指定不用index merge的hint。

多范围读MRR

多范围读MRR概念

多范围读即Multi-Range Read (MRR)的主要作用是减少扫描辅助索引时产生的IO随机读。优化器一般先读取索引覆盖的字段,其它字段通过rowid关联到聚集索引而找出。多范围读(Multi-Range Read)优化可用于使用索引的范围扫描和等价连接,它不支持虚拟列上的二级索引。

具体的说当读取到辅助索引里字段后再关联到主键,对主键在内存里进行排序后再顺序的读取其它字段的信息。这样就会减少IO随机读。

InnoDB多范围读优化(MRR)的主要应用于没有覆盖索引的磁盘受限查询(当写比读快很多时,读就即被认为是磁盘受限)。

优化参数

用optimizer_switch的参数来控制是否使用MRR.设置mrr=on时,表示启用MRR优化。

mrr_cost_based表示是否通过cost base的方式来启用MRR。

当mrr=on,mrr_cost_based=on,则表示cost base的方式还选择启用MRR优化,当发现优化后的代价过高时就会不使用该项优化。

当mrr=on,mrr_cost_based=off,则表示总是开启MRR优化。

多访问读示例

mysql> EXPLAIN
SELECT /*+ MRR(city) */
*
FROM world.city
WHERE CountryCode BETWEEN 'AUS' AND 'CHN'\\G
**************************** 1. row *****************************
id: 1
select_type: SIMPLE
table: city
partitions: NULL
type: range
possible_keys: CountryCode
key: CountryCode
key_len: 3
ref: NULL
rows: 812
filtered: 100
Extra: Using index condition; Using MRR

批量键访问Batched Key Access

批量键访问概念

批量键值访问(Batched Key Access)简称BKA,该优化操作是块内嵌循环和多范围读的整合。这样非索引联接就可以使用类似连接缓冲区索引联接的方式进行优化,通过使用多范围读取优化来减少随机I/O的数量。

由于批处理键值访问优化主要受益的查询范围相对较窄,而且其他查询的性能可能会下降,因此默认情况下禁用优化。

批量键访问示例

SET SESSION
optimizer_switch
= 'mrr=on,mrr_cost_based=off,batched_key_access=on';

mysql>  EXPLAIN
    -> SELECT /*+ BKA(ci) */
    -> co.Code, co.Name AS Country,
    -> ci.Name AS City
    -> FROM world.country co
    -> INNER JOIN world.city ci
    -> ON ci.CountryCode = co.Code\\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: co
   partitions: NULL
         type: ALL
possible_keys: PRIMARY
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 239
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: ci
   partitions: NULL
         type: ref
possible_keys: CountryCode
          key: CountryCode
      key_len: 12
          ref: world.co.Code
         rows: 17
     filtered: 100.00
        Extra: Using join buffer (Batched Key Access)

In this example, the Batched Key Access is enabled using an optimizer hint for the
join on the city (ci) table using the CountryCode index

上例中通过优化器提示(hint)启用了批量键访问选项,即在关联city表时用到CountryCode索引。

批量键值范围优化主要应用在数据量大的关联中,关联缓存则要相应的设置的多些,一般它对应的参数join_buffer_size要设置成4M或者更大。

以上是关于MySQL 优化器之Index merge Multi-Range Read MRR与Batched Key Access使用案例详解的主要内容,如果未能解决你的问题,请参考以下文章

MySQL-优化之 index merge(索引合并)

MySQL 优化之 index merge(索引合并)

MySQL 优化之 index_merge (索引合并)

Mysql 查询优化器之派生条件回移Derived Condition Pushdown详解

Mysql 查询优化器之派生条件回移Derived Condition Pushdown详解

MySQL 5.6比较重要的参数,以及5.5到5.6默认值有过变化的参数