인덱스 : 데이터를 빠르게 찾기 위한 ‘검색용 지도’
→ B-Tree 알고리즘을 사용해 별도의 자료 구조로 관리
| 구분 | 인덱스 없음 | 인덱스 있음 |
|---|---|---|
| 검색 방식 | 전체 테이블 탐색 | 정렬된 인덱스 탐색 |
| 속도 | O(n) | O(log n) |
| I/O 비용 | 매우 높음 | 낮음 |
| 추가 공간 | 없음 | 필요 → 인덱스가 많아지만, 남은 DB 공간이 줄어들게 됨 |
| INSERT/UPDATE | 빠름 | 약간 느림 → 데이터를 자료 구조에 저장해야 하기 때문 |
인덱스의 한계
⇒ 마구잡이로 인덱스를 사용하지 않고, 필요한 경우에만 인덱스를 사용해야 함!
B-Tree 구조
배열구조
Index: 0 1 2 3 4 ...
Value: [10] [20] [30] [40] [50] ...
--------------------------------------
트리 구조 : 작으면 왼쪽, 크면 오른쪽으로 이동
[50] # 60 > 50 : 오른쪽으로 이동
/ \\
[20] [70] # 60 < 70 : 왼쪽으로 이동
/ \\ / \\
[10] [30] [60] [80] # 데이터 주소를 통해 실제 레코드에 접근
=> DB Full Scan 대비 수백~수천 배 빠르게 동작함
인덱스 스캔의 종류
Unique Scan : 정확한 한 건의 데이터를 찾는 탐색
인덱스가 Primary Key 또는 Unique 제약일 때 발생
⇒ B-Tree에서 정확히 한 경로만 따라가면 되기 때문에, 탐색 속도가 가장 빠름
SELECT * FROM users WHERE id = 10;
Range Scan : 일정 범위(구간)의 데이터를 연속적으로 읽는 탐색
Between, >, <, LIKE 등의 조건에서 발생
⇒ B-Tree의 구조를 활용해 시작 위치를 찾고, 순서대로 이어서 읽음
SELECT * FROM orders
WHERE order_date BETWEEN '2025-11-01' AND '2025-11-10';
Full Index Scan : 인덱스 전체를 순서대로 읽는 탐색
Where 조건이 없어서 인덱스 전체를 탐색할 때 사용 → 이미 정렬되어 있어 ORDER BY 시 추가 정렬이 필요 없음
SELECT * FROM users ORDER BY name;
Index Condition Pushdown(ICP) : 필요한 조건을 인덱스 탐색 중에 미리 적용
Where 조건 중 일부를 인덱스 단계에서 미리 필터링
⇒ 불필요한 데이터 접근을 최소화해서 디스크 I/O 감소함
SELECT * FROM orders
WHERE status = 'PAID' AND order_date > '2025-11-01';
# range scan이 상대적으로 느리기 때문에, status = 'PAID'로 원하는 대상만 필터링하고, 기준대로 검색
Table Full Scan : 인덱스를 전혀 사용하지 않는 탐색(모든 데이터를 처음부터 끝까지 확인)
<aside> 💡
옵티마이저(Optimizer) : DB가 스스로 가장 효율적인 실행 방법을 고르는 두뇌
⇒ 개발자가 인덱스만 잘 만들어두면, 어떤 인덱스를 사용할지는 DB가 자동으로 결정함
</aside>