1️⃣ Index Merge란?
Index Merge는 MySQL/MariaDB 옵티마이저가:
“단일 인덱스로는 최적화가 안 되니까
여러 인덱스를 각각 타서
결과를 합쳐보자”
라고 판단했을 때 선택하는 실행 계획이다.
EXPLAIN에 보통 이렇게 나타난다.
type: index_merge
Extra: Using intersect(...)
2️⃣ 옵티마이저가 Index Merge를 선택하는 상황
테이블에 이런 인덱스가 있을 때
-- 인덱스
IDX_A (COMPCD)
IDX_B (YEARXX)
-- 쿼리
WHERE COMPCD = ?
AND YEARXX = ?
- 단일 복합 인덱스 ❌
- 두 개의 단일 인덱스만 존재
👉 옵티마이저는:
- COMPCD 인덱스 스캔
- YEARXX 인덱스 스캔
- 두 결과의 교집합 계산
3️⃣ 왜 느린가? (핵심 이유 5가지)
① “두 번 이상 읽는다”
- 인덱스 A 전체 스캔
- 인덱스 B 전체 스캔
👉 단일 복합 인덱스는 한 번만 탐색
② 임시 집합 계산 비용
- PK 목록을 메모리에 저장
- 교집합/합집합 계산
👉 이 자체가 CPU + 메모리 비용
③ LIMIT / early stop 불가
Index Merge는:
- 조건 만족 row를 전부 수집
- 그 후에야 LIMIT 적용
👉 페이징 / EXISTS / 커서와 궁합 최악
④ Covering Index 효과 없음
- 결국 PK 목록만 얻음
- 테이블 접근은 그대로 발생
👉 랜덤 I/O 줄어들지 않음
⑤ 데이터 분포 변화에 취약
- 인덱스 통계 조금만 틀려도
- 옵티마이저 판단 급변
👉 실행 계획 안정성 ❌
4️⃣ Index Merge vs 복합 인덱스 (비교)
| 인덱스 탐색 | 여러 번 | 한 번 |
| 정렬 지원 | ❌ | ✅ |
| LIMIT 최적화 | ❌ | ✅ |
| Covering Index | ❌ | ✅ |
| 실행 계획 안정성 | 낮음 | 높음 |
5️⃣ Index Merge가 특히 치명적인 패턴
❌ AND 조건 + 페이징
❌ EXISTS / 서브쿼리
6️⃣ “Index Merge가 나쁘기만 한가?” (예외)
⚠️ 아주 제한적인 경우
- 단일 인덱스만 있고
- 결과 집합이 매우 작고
- 임시 최적화가 필요할 때
👉 하지만 이마저도:
- UNION ALL
- 쿼리 분리
가 더 나은 경우가 많다.
7️⃣ 실무에서의 원칙 (중요)
Index Merge가 보이면
“인덱스가 부족하다는 신호”로 받아들여야 한다.
8️⃣ 결론
Index Merge는
“여러 인덱스를 쓰는 최적화”가 아니라
“복합 인덱스가 없어서 어쩔 수 없이 쓰는 차선책”이다.
9️⃣ 질문
만약 적절한 인덱스가 있는데, Index Merge가 시행되는 경우도 있을까?
👉 “알맞은 인덱스가 있음에도 Index Merge가 실행되는 경우는 실제로 존재한다.”
다만 그건 **인덱스 설계 문제가 아니라 ‘옵티마이저 판단 문제’**인 경우가 대부분
1. 가장 흔한 경우: 옵티마이저가 “더 싸다고 착각”할 때
-- 복합 인덱스 (정답)
IDX_C (COMPCD, PLCBUS, YEARXX)
-- 단일 인덱스들
IDX_A (COMPCD)
IDX_B (PLCBUS)
-- 쿼리
WHERE COMPCD = ?
AND PLCBUS = ?
👉 사람 입장:
“복합 인덱스 IDX_C 타는 게 당연하지 않나?”
👉 옵티마이저 입장(통계가 틀리면):
- COMPCD = ? → 결과 많음
- PLCBUS = ? → 결과 많음
- “둘을 merge 하는 게 싸겠는데?”
➡️ Index Merge 선택
2. OR / IN 조건이 섞인 경우
WHERE COMPCD = ?
AND (PLCBUS = ? OR YEARXX = ?)
- 복합 인덱스가 있어도
- OR 조건 때문에 인덱스 경로가 갈라짐
👉 옵티마이저는:
- PLCBUS 인덱스 탐색
- YEARXX 인덱스 탐색
- 결과 merge
➡️ Index Merge 강제 발생
📌 이건 인덱스 문제가 아니라 쿼리 구조 문제
🔟 Index Merge를 없애기
1. OR → UNION ALL (가장 강력)
OR가 느려지는 핵심 이유
OR는 조건이 “갈라지기” 때문에:
- 한 인덱스로 한 번에 못 푸는 경우가 많고
- 옵티마이저가
- Index Merge(여러 인덱스 결과 합치기)
- filesort/temp
쪽으로 빠지기 쉽습니다.
UNION ALL이 빨라지는 핵심 이유
UNION ALL은 아예 쿼리를 둘로 나눠서:
- 각각이 자기 조건에 맞는 최적 인덱스를 타고
- 결과를 그냥 이어 붙입니다(ALL이니까 중복제거 없음)
즉,
OR: “DB가 알아서 합쳐줘(근데 합치는 비용이 큼)”
UNION ALL: “내가 분기해줄게, 너는 각자 인덱스만 잘 타”
이 차이입니다.
2. IN(대량) → JOIN(임시 집합)
IN 값이 수십~수백 이상 커지면, 내부적으로 비용이 커지고 계획이 흔들립니다.
리팩토링
- 값 목록을 임시 테이블/파생 테이블로 만들고 JOIN
- (애플리케이션에서 리스트를 테이블로 넣을 수 있으면 더 좋음)
✅ 옵티마이저가 더 안정적으로 플랜을 고름
✅ 실행 계획이 덜 흔들림
3. 함수/가공된 조건 제거 (SARGable 만들기)
인덱스가 있어도 아래와 같은 조건이면 의미 없음
WHERE DATE(REGDT) = '2026-01-06'
아래같이 작성해야 함
WHERE REGDT >= '2026-01-06 00:00:00'
AND REGDT < '2026-01-07 00:00:00'
✅ 인덱스 레인지 스캔 가능
✅ filesort/merge 위험 감소
4. 정렬/페이징을 먼저 줄이기 (2단계 조회)
Index Merge가 뜨는 큰 이유 중 하나가
“조건 + 정렬 + LIMIT”을 한 번에 못 풀기 때문입니다.
추천 구조
- 커버링 인덱스로 PK 30개만 추림
- PK로 본문 30개만 가져오기
-- 1단계 (커버링)
SELECT DOCSEQ
FROM HERGMT
WHERE COMPCD = ?
AND PLCBUS = ?
ORDER BY DOCSEQ DESC
LIMIT 30;
-- 2단계
SELECT *
FROM HERGMT
WHERE DOCSEQ IN ( ...30개... );
✅ 정렬 부담 급감
✅ 인덱스 선택이 안정적
'CS > SQL' 카테고리의 다른 글
| Covering Index (커버링 인덱스) (0) | 2026.01.06 |
|---|---|
| 인덱스 설계 사고방식 2 (0) | 2026.01.06 |
| 인덱스 설계 사고방식 1 (0) | 2026.01.06 |
| 재귀 쿼리(Recursive Query) (0) | 2025.10.01 |
| SQL 동적 쿼리(Dynamic SQL) (0) | 2025.10.01 |