카테고리 : MySQL/기술노트

Multi-Range Read와 Batched Key Access: 랜덤 IO를 줄이는 최적화

MySQL Multi-Range Read와 Batched Key Access가 보조 인덱스 조회와 조인에서 랜덤 I/O를 줄이는 원리를 운영 관점에서 정리한다.

저자: MySQL 기술 노트 작성: 2026.07.08 약 13분 7,271자
다운로드

Multi-Range Read와 Batched Key Access: 랜덤 IO를 줄이는 최적화

MySQL에서 인덱스를 사용한다는 말은 항상 순차적이고 저렴한 접근을 의미하지 않는다. 특히 InnoDB 보조 인덱스를 통해 후보 row를 찾은 뒤 clustered index로 다시 접근하는 실행 경로에서는, 후보 row의 primary key 값이 물리적으로 흩어져 있으면 많은 랜덤 I/O가 발생할 수 있다. 데이터가 buffer pool에 충분히 올라와 있는 작은 시스템에서는 이 차이가 잘 보이지 않지만, 운영 환경의 큰 테이블·넓은 range scan·조인 쿼리에서는 같은 인덱스를 사용해도 실제 지연 시간이 크게 달라질 수 있다.

Multi-Range Read(MRR)는 range scan 또는 보조 인덱스 조회에서 얻은 row id를 모아 더 효율적인 순서로 테이블 row를 읽게 하는 최적화다. Batched Key Access(BKA)는 nested loop join에서 안쪽 테이블의 인덱스 lookup을 한 건씩 즉시 수행하지 않고, 바깥쪽 row의 join key를 batch로 모아 MRR 방식으로 접근하도록 돕는 조인 최적화다. 두 기능은 서로 다른 이름을 갖지만, 운영 관점에서는 “흩어진 row 접근을 모아서 랜덤 I/O 비용을 낮추려는 계층적 최적화”로 함께 이해하는 것이 좋다.

이 글에서는 MRR과 BKA가 실행 경로의 어느 위치에서 동작하는지, optimizer_switchEXPLAIN에서 어떻게 확인하는지, 어떤 상황에서 이익과 손해가 갈리는지, 그리고 Aurora MySQL 운영에서는 어떤 점을 다르게 보아야 하는지를 정리한다. 예제는 MySQL 8.0 기준으로 검증 가능한 형태를 사용한다.

1. 보조 인덱스 조회에서 랜덤 I/O가 생기는 이유

InnoDB 테이블은 primary key 기준으로 clustered index에 row를 저장한다. 보조 인덱스는 보조 key 값과 primary key 값을 함께 보관한다. 따라서 보조 인덱스만으로 필요한 컬럼을 모두 얻는 covering index 경로가 아니라면, MySQL은 보조 인덱스 엔트리를 읽은 뒤 그 엔트리에 들어 있는 primary key로 clustered index row를 다시 읽어야 한다.

예를 들어 (region, created_at) 보조 인덱스로 한 달치 주문을 찾는다고 하자. 보조 인덱스에서는 region='KR', created_at BETWEEN ... 조건에 해당하는 엔트리를 비교적 연속적으로 읽을 수 있다. 그러나 실제 row는 primary key 순서로 저장되어 있으므로, 보조 인덱스에서 나온 primary key가 1, 80001, 240, 90010, ...처럼 흩어져 있으면 clustered index 접근은 랜덤 lookup이 된다.

이 경로는 다음과 같이 단순화할 수 있다.

graph TD
    A[보조 인덱스 range scan] --> B[조건에 맞는 보조 인덱스 엔트리 발견]
    B --> C[엔트리에서 primary key 수집]
    C --> D{MRR 사용 여부}
    D -- 미사용 --> E[발견 즉시 clustered index row 랜덤 lookup]
    D -- 사용 --> F[row id를 batch로 모음]
    F --> G[primary key 순서 또는 더 효율적인 순서로 정렬]
    G --> H[clustered index row를 묶어서 읽음]
    E --> I[서버 계층으로 row 반환]
    H --> I

MRR의 핵심은 “인덱스를 사용하느냐”가 아니라 “인덱스에서 찾은 row를 어떤 순서로 실제 테이블에서 읽느냐”다. 따라서 MRR은 보조 인덱스 lookup이 많고, 그 lookup이 테이블 row 접근을 동반하며, row들이 물리적으로 흩어져 있을수록 의미가 커진다. 반대로 covering index만으로 쿼리가 끝나거나 후보 row 수가 매우 적으면 MRR의 batch 구성·정렬 비용이 이익보다 클 수 있다.

2. MRR의 내부 동작: 후보 row id를 모아서 접근 순서를 바꾼다

MRR은 MySQL executor와 스토리지 엔진 사이의 row 읽기 방식을 바꾼다. 전통적인 range scan은 인덱스 엔트리를 하나 읽고, 필요한 경우 즉시 base row를 읽고, 다시 다음 인덱스 엔트리로 넘어가는 흐름에 가깝다. MRR은 이 흐름 사이에 buffer를 둔다. 보조 인덱스에서 얻은 row id를 일정량 모은 뒤, row id 기준으로 정렬하거나 스토리지 엔진이 더 효율적으로 읽을 수 있는 순서로 재배열한 다음 base row를 가져온다.

이 구조는 디스크 기반 랜덤 I/O를 줄이는 데 유리하다. row id가 어느 정도 정렬되어 있으면 같은 페이지 또는 인접 페이지 접근이 늘어날 수 있고, buffer pool miss가 발생하더라도 더 예측 가능한 접근 패턴을 만들 수 있다. 다만 모든 환경에서 항상 빨라지는 것은 아니다. row id를 모으는 메모리, 정렬 비용, batch를 채우기까지의 지연, 이미 buffer pool에 올라와 있는 페이지 비율이 함께 영향을 준다.

MRR 관련 판단은 optimizer_switchmrr, mrr_cost_based 항목과 관련이 깊다. mrr=on은 MRR 사용 가능성을 열어 두는 설정이고, mrr_cost_based=on이면 옵티마이저가 비용 모델상 유리하다고 판단할 때만 MRR을 선택한다. 테스트나 재현 예제에서는 MRR 적용을 분명히 보기 위해 mrr_cost_based=off로 두기도 하지만, 운영 기본값을 무조건 바꾸는 것은 별개의 문제다.

다음 예제는 보조 인덱스 range scan에서 Using MRR이 표시되는 형태를 확인한다. 작은 재현 테이블이므로 성능 차이를 측정하는 예제가 아니라, 실행 계획에 MRR 경로가 나타나는지 확인하는 구조다.

DROP TABLE IF EXISTS mrr_orders;
CREATE TABLE mrr_orders (
  id BIGINT NOT NULL PRIMARY KEY,
  customer_id BIGINT NOT NULL,
  region CHAR(2) NOT NULL,
  status VARCHAR(16) NOT NULL,
  created_at DATE NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  KEY idx_region_created (region, created_at),
  KEY idx_customer (customer_id)
) ENGINE=InnoDB;

INSERT INTO mrr_orders VALUES
  (1, 101, 'KR', 'PAID', '2026-01-01', 10.00),
  (2, 102, 'KR', 'PAID', '2026-01-02', 20.00),
  (3, 103, 'KR', 'PAID', '2026-01-03', 30.00),
  (4, 104, 'US', 'PAID', '2026-01-04', 40.00),
  (5, 105, 'US', 'PAID', '2026-01-05', 50.00),
  (6, 106, 'JP', 'PAID', '2026-01-06', 60.00),
  (7, 107, 'JP', 'CANCELLED', '2026-01-07', 70.00),
  (8, 108, 'KR', 'PAID', '2026-01-08', 80.00);

SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
EXPLAIN SELECT * FROM mrr_orders FORCE INDEX (idx_region_created)
WHERE region IN ('KR','US') AND created_at BETWEEN '2026-01-01' AND '2026-01-31';
DROP TABLE mrr_orders;

실행 결과(MySQL 8.0.x):

mysql> CREATE TABLE mrr_orders (...);
Query OK, 0 rows affected

mysql> INSERT INTO mrr_orders VALUES (...);
Query OK, 8 rows affected
Records: 8  Duplicates: 0  Warnings: 0

mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
Query OK, 0 rows affected

mysql> EXPLAIN SELECT * FROM mrr_orders FORCE INDEX (idx_region_created)
    -> WHERE region IN ('KR','US') AND created_at BETWEEN '2026-01-01' AND '2026-01-31';
+----+-------------+------------+------------+-------+--------------------+--------------------+---------+------+------+----------+----------------------------------+
| id | select_type | table      | partitions | type  | possible_keys      | key                | key_len | ref  | rows | filtered | Extra                            |
+----+-------------+------------+------------+-------+--------------------+--------------------+---------+------+------+----------+----------------------------------+
|  1 | SIMPLE      | mrr_orders | NULL       | range | idx_region_created | idx_region_created | 11      | NULL |    2 |   100.00 | Using index condition; Using MRR |
+----+-------------+------------+------------+-------+--------------------+--------------------+---------+------+------+----------+----------------------------------+
1 row in set

이 예제에서 FORCE INDEXmrr_cost_based=off는 운영 권장 설정이 아니라 재현을 안정화하기 위한 장치다. 실제 운영에서는 비용 모델이 선택한 기본 계획, 통계 정보, row 수, buffer pool 상태, 쿼리 지연 시간의 변화를 함께 확인해야 한다.

3. BKA는 nested loop join의 안쪽 lookup을 batch화한다

BKA는 MRR을 조인으로 확장해서 이해할 수 있다. 일반적인 nested loop join은 바깥쪽 테이블에서 row를 하나 읽고, 그 row의 join key로 안쪽 테이블을 인덱스 lookup한다. 바깥쪽 row가 많고 안쪽 테이블의 lookup 대상이 흩어져 있으면, 안쪽 테이블에 대해 많은 랜덤 접근이 발생한다.

BKA는 이 과정을 한 row 단위가 아니라 batch 단위로 바꾼다. 바깥쪽 row의 join key를 join buffer에 모은 뒤, 안쪽 테이블에 대한 key lookup을 묶어서 수행한다. 이때 MRR을 사용하면 안쪽 테이블의 row 접근 순서를 더 효율적으로 만들 수 있다.

sequenceDiagram
    participant O as 바깥쪽 테이블
    participant J as Join buffer
    participant I as 안쪽 테이블 인덱스
    participant T as 안쪽 테이블 row

    O->>J: join key를 batch로 적재
    J->>I: 여러 key lookup을 한 번에 요청
    I->>J: 매칭 row id 반환
    J->>J: row id 정렬 또는 접근 순서 최적화
    J->>T: 안쪽 row를 묶어서 읽음
    T->>O: 조인 결과 생성

EXPLAINExtraUsing join buffer (Batched Key Access)가 표시되면 BKA가 적용된 것이다. BKA는 batched_key_access optimizer switch와 관련이 있으며, 보통 MRR이 함께 켜져 있어야 의미가 있다. 또한 BKA는 join buffer를 사용하므로 join_buffer_size의 영향을 받는다. 너무 작은 buffer는 batch 이익을 제한할 수 있고, 너무 큰 buffer를 전역으로 키우면 동시 접속이 많은 환경에서 세션별 메모리 사용량이 커질 수 있다.

다음 예제는 작은 고객·주문 테이블을 만들고 BKA 표시를 확인한다. BKA(o) optimizer hint는 안쪽 테이블 o에 BKA를 유도하기 위한 재현용 힌트다.

DROP TABLE IF EXISTS bka_customers;
DROP TABLE IF EXISTS bka_orders;
CREATE TABLE bka_customers (
  customer_id BIGINT NOT NULL PRIMARY KEY,
  tier VARCHAR(16) NOT NULL
) ENGINE=InnoDB;
CREATE TABLE bka_orders (
  order_id BIGINT NOT NULL PRIMARY KEY,
  customer_id BIGINT NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  KEY idx_customer (customer_id)
) ENGINE=InnoDB;
INSERT INTO bka_customers VALUES (101,'GOLD'),(102,'SILVER'),(103,'GOLD'),(104,'BRONZE');
INSERT INTO bka_orders VALUES (1,101,10),(2,101,20),(3,102,30),(4,103,40),(5,104,50),(6,103,60);
SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
EXPLAIN SELECT /*+ BKA(o) */ c.customer_id, o.order_id, o.amount
FROM bka_customers c JOIN bka_orders o ON o.customer_id = c.customer_id
WHERE c.tier IN ('GOLD','SILVER');
DROP TABLE bka_orders;
DROP TABLE bka_customers;

실행 결과(MySQL 8.0.x):

mysql> CREATE TABLE bka_customers (...);
Query OK, 0 rows affected

mysql> CREATE TABLE bka_orders (...);
Query OK, 0 rows affected

mysql> INSERT INTO bka_customers VALUES (...);
Query OK, 4 rows affected
Records: 4  Duplicates: 0  Warnings: 0

mysql> INSERT INTO bka_orders VALUES (...);
Query OK, 6 rows affected
Records: 6  Duplicates: 0  Warnings: 0

mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
Query OK, 0 rows affected

mysql> EXPLAIN SELECT /*+ BKA(o) */ c.customer_id, o.order_id, o.amount
    -> FROM bka_customers c JOIN bka_orders o ON o.customer_id = c.customer_id
    -> WHERE c.tier IN ('GOLD','SILVER');
+----+-------------+-------+------------+------+---------------+--------------+---------+-------------------------------+------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key          | key_len | ref                           | rows | filtered | Extra                                  |
+----+-------------+-------+------------+------+---------------+--------------+---------+-------------------------------+------+----------+----------------------------------------+
|  1 | SIMPLE      | c     | NULL       | ALL  | PRIMARY       | NULL         | NULL    | NULL                          |    4 |    50.00 | Using where                            |
|  1 | SIMPLE      | o     | NULL       | ref  | idx_customer  | idx_customer | 8       | mysql_tech_note.c.customer_id |    1 |   100.00 | Using join buffer (Batched Key Access) |
+----+-------------+-------+------------+------+---------------+--------------+---------+-------------------------------+------+----------+----------------------------------------+
2 rows in set

BKA는 조인 성능을 항상 개선하지 않는다. 바깥쪽 row가 적거나 안쪽 lookup이 이미 매우 선택적이고 buffer pool hit ratio가 높으면, batch 구성 비용이 이익을 상쇄할 수 있다. 반대로 대량 row를 조인하면서 안쪽 테이블 접근이 랜덤하게 흩어지고, 디스크 또는 원격 스토리지 지연이 병목이면 이익이 커질 수 있다.

4. optimizer_switch와 운영 설정 해석

MRR과 BKA는 주로 다음 optimizer switch와 관련된다.

항목 의미 운영 해석
mrr MRR 최적화 사용 가능 여부 기본적으로 켜져 있어도 비용 모델이 실제 선택 여부를 결정할 수 있다.
mrr_cost_based 비용 기반으로 MRR 선택 여부 판단 운영에서는 보통 비용 기반 판단을 존중하고, 재현·검증 시에만 끄는 편이 안전하다.
batched_key_access BKA 조인 방식 사용 가능 여부 조인 buffer와 MRR 조건이 맞을 때 의미가 있다.
block_nested_loop join buffer 기반 nested loop 계열 최적화 MySQL 8.0의 해시 조인 변화와 함께 버전별 의미를 확인해야 한다.

운영자가 피해야 할 가장 흔한 실수는 특정 기능 이름만 보고 전역 설정을 바꾸는 것이다. 예를 들어 batched_key_access=on을 전역으로 켰더니 일부 대량 조인은 좋아졌지만, 다른 OLTP 쿼리에서 메모리 사용량과 plan 변동이 증가할 수 있다. 반대로 MRR이 EXPLAIN에 보이지 않는다고 해서 무조건 설정이 잘못된 것도 아니다. 옵티마이저가 후보 row 수, 인덱스 선택도, cost model, 통계 정보를 보고 MRR을 쓰지 않는 편이 낫다고 판단했을 수 있다.

안전한 접근 순서는 다음과 같다.

  1. 문제 쿼리의 현재 실행 계획과 실제 지연 시간을 기록한다.
  2. EXPLAIN, EXPLAIN ANALYZE, optimizer_trace 또는 Performance Schema로 병목이 row lookup인지 확인한다.
  3. 세션 단위로 optimizer_switch와 힌트를 적용해 plan 차이를 비교한다.
  4. 성능 차이가 확인되면 쿼리 힌트, 인덱스 재설계, 통계 갱신, 제한적인 세션 설정 중 어떤 방식이 가장 안전한지 결정한다.
  5. 전역 설정 변경은 여러 workload에 대한 회귀 테스트 후 적용한다.

5. MRR/BKA가 유리한 패턴과 그렇지 않은 패턴

MRR과 BKA가 유리한 전형적 패턴은 다음과 같다.

패턴 기대 효과
보조 인덱스 range scan 후 많은 base row를 읽는다 clustered index 랜덤 lookup을 줄일 가능성이 있다.
IN (...) 또는 여러 range 조건으로 후보 row id가 많이 나온다 row id를 모아 접근 순서를 바꿀 여지가 생긴다.
nested loop join에서 안쪽 테이블 lookup이 많이 반복된다 BKA가 join key를 batch화해 안쪽 접근을 줄일 수 있다.
데이터가 buffer pool보다 크고 I/O 지연이 주요 병목이다 랜덤 I/O 감소 효과가 지연 시간에 반영될 가능성이 크다.

반대로 다음 상황에서는 기대 효과가 작거나 plan이 불리해질 수 있다.

패턴 주의점
covering index로 쿼리가 끝난다 base row lookup이 없으므로 MRR의 핵심 이익이 줄어든다.
후보 row 수가 매우 적다 batch 구성·정렬 비용이 더 클 수 있다.
결과 정렬 순서가 중요하고 별도 sort 비용이 커진다 접근 순서 최적화가 전체 plan에는 불리할 수 있다.
buffer pool hit ratio가 매우 높다 물리 I/O 절감 효과가 지연 시간에 작게 나타날 수 있다.
조인 순서나 인덱스 선택 자체가 잘못되었다 BKA는 잘못된 plan을 근본적으로 고치지 못한다.

특히 BKA는 “조인을 빠르게 만드는 스위치”가 아니라 “특정 nested loop join에서 안쪽 lookup의 I/O 패턴을 바꾸는 방식”이다. 조인 순서가 잘못되어 바깥쪽 row가 과도하게 많거나, 안쪽 테이블에 적절한 인덱스가 없으면 BKA만으로 문제를 해결하기 어렵다. 이런 경우에는 통계 정보, 히스토그램, 복합 인덱스, 조건식 재작성, 조인 순서 힌트까지 함께 검토해야 한다.

6. Aurora MySQL에서의 해석

Aurora MySQL은 MySQL 호환 엔진이지만 스토리지 구조가 일반적인 단일 인스턴스 로컬 디스크 InnoDB와 다르다. Aurora의 분산 스토리지 계층은 데이터 페이지를 여러 스토리지 노드에 분산·복제하고, compute 노드는 buffer cache와 SQL 실행을 담당한다. 따라서 랜덤 I/O의 물리적 의미와 지연 특성이 온프레미스 또는 단일 VM의 로컬 SSD MySQL과 완전히 같지는 않다.

그렇다고 MRR/BKA의 의미가 사라지는 것은 아니다. 보조 인덱스 lookup 후 base row를 많이 읽는 실행 경로는 여전히 buffer cache miss, 스토리지 계층 read, 네트워크 왕복, CPU 및 메모리 압박을 만들 수 있다. Aurora에서는 다음 관점이 특히 중요하다.

  • Performance Insights와 wait event 확인: 쿼리 지연이 CPU인지, lock인지, storage read 계열 wait인지 먼저 구분해야 한다.
  • buffer cache hit와 working set 크기: 같은 plan도 cache가 따뜻한 상태와 cold 상태에서 차이가 크다.
  • reader 인스턴스별 cache 차이: writer와 reader, 또는 여러 reader 사이에서 cache 상태가 다르면 같은 쿼리의 MRR/BKA 효과가 다르게 보일 수 있다.
  • 파라미터 그룹 변경 범위: optimizer switch를 전역 파라미터로 바꾸면 클러스터 또는 인스턴스 단위 영향 범위를 정확히 확인해야 한다.

Aurora에서도 우선 세션 단위 검증과 쿼리 단위 힌트로 plan 변화를 관찰하고, 전역 파라미터 변경은 제한적으로 접근하는 편이 안전하다.

7. 진단과 튜닝 절차

MRR/BKA를 검토할 때는 기능 적용 여부보다 “랜덤 row lookup이 실제 병목인가”를 먼저 확인해야 한다. 다음 절차를 권장한다.

  1. 문제 쿼리 식별
    Performance Schema, slow query log, Aurora Performance Insights 등에서 평균 지연 시간뿐 아니라 p95/p99 지연, rows examined, 실행 빈도를 함께 본다.

  2. 실행 계획 확인
    EXPLAIN에서 보조 인덱스 range/ref 접근, rows, filtered, ExtraUsing MRR, Using join buffer (Batched Key Access) 여부를 확인한다. EXPLAIN ANALYZE를 사용할 수 있으면 추정 row와 실제 row 차이도 본다.

  3. 세션 단위 비교
    운영 트래픽에 바로 적용하지 말고, 동일한 세션에서 optimizer_switch를 바꿔 비교한다. 재현 목적이라면 mrr_cost_based=off를 사용할 수 있지만, 운영 적용 판단은 기본 비용 모델에서의 plan과 함께 비교해야 한다.

  4. 인덱스 설계와 비교
    MRR/BKA가 도움이 되더라도, 더 적절한 복합 인덱스나 covering index가 있으면 그쪽이 더 근본적인 해결책일 수 있다. 랜덤 lookup을 줄이는 것보다 lookup 자체를 줄이는 편이 대개 더 강력하다.

  5. 메모리 영향 평가
    BKA는 join buffer를 사용하므로 동시성 높은 환경에서 join_buffer_size를 전역으로 크게 올리는 방식은 위험할 수 있다. 세션별 메모리 항목은 접속 수와 곱해져 상한을 만들 수 있음을 고려해야 한다.

  6. 회귀 테스트
    특정 쿼리 하나만 보고 optimizer switch를 변경하면 다른 쿼리의 plan이 바뀔 수 있다. 대표 workload 또는 최소한 상위 쿼리 집합에 대해 plan과 지연 시간 변화를 검증한다.

8. 흔한 오해와 장애 모드

첫 번째 오해는 Using MRR이 보이면 반드시 빠르다는 생각이다. EXPLAIN은 실행 계획의 구조를 보여 주지만, 실제 시스템의 cache 상태와 I/O 지연, 동시성, row 크기까지 모두 보장하지 않는다. 작은 테이블에서는 MRR이 표시되어도 성능 차이가 거의 없을 수 있고, 오히려 batch 처리 비용이 눈에 띌 수 있다.

두 번째 오해는 BKA가 인덱스 부재를 보완한다고 보는 것이다. BKA는 안쪽 테이블을 효율적으로 lookup할 수 있는 인덱스가 있을 때 가치가 있다. 안쪽 테이블이 full scan이면 BKA가 아니라 조인 순서·조건·인덱스 설계가 먼저다.

세 번째 오해는 join_buffer_size를 크게 하면 BKA가 항상 좋아진다는 생각이다. join buffer는 세션 단위 메모리로 사용될 수 있으므로, 전역 값을 크게 키우면 피크 접속 수에서 메모리 압박이 생긴다. 또한 너무 큰 batch가 항상 좋은 것도 아니다. 지연 시간, CPU cache, 메모리 할당 비용, executor 흐름이 함께 영향을 받는다.

네 번째 장애 모드는 통계 정보 부정확성이다. 옵티마이저가 후보 row 수를 크게 잘못 추정하면 MRR/BKA 선택 여부보다 먼저 인덱스와 조인 순서 자체가 흔들릴 수 있다. 이런 경우 ANALYZE TABLE, persistent statistics, histogram, 데이터 분포 확인을 함께 수행해야 한다.

9. 운영 체크리스트

MRR/BKA를 검토하거나 운영에 반영하기 전에 다음 항목을 확인한다.

  • EXPLAINrows, filtered, Extra
  • EXPLAIN ANALYZE
  • 세션 단위 optimizer_switch
  • join_buffer_size

10. 결론

MRR과 BKA는 MySQL 옵티마이저가 단순히 “어떤 인덱스를 쓸 것인가”를 넘어서 “찾은 row를 어떤 순서와 묶음으로 읽을 것인가”까지 고려한다는 점을 보여 주는 기능이다. MRR은 보조 인덱스에서 얻은 row id를 모아 clustered index 접근의 랜덤성을 줄이고, BKA는 nested loop join의 안쪽 lookup을 batch화해 같은 문제를 조인 실행 경로에서 완화한다.

운영자는 이 기능을 스위치 이름 중심으로 외우기보다, 보조 인덱스 lookup, clustered index 접근, join buffer, 비용 모델, buffer pool 상태의 연결로 이해해야 한다. MRR/BKA는 적절한 상황에서 유용한 도구지만, 잘못된 인덱스 설계나 부정확한 통계 정보를 대신 해결하지는 않는다. 다음 단계에서는 이러한 실행 계획 최적화가 실제 운영 지표와 어떻게 연결되는지, 특히 EXPLAIN ANALYZE와 Performance Schema를 함께 해석하는 방법을 계속 다룰 수 있다.