1. Index

인덱스 : 데이터를 빠르게 찾기 위한 ‘검색용 지도’

→ B-Tree 알고리즘을 사용해 별도의 자료 구조로 관리

구분 인덱스 없음 인덱스 있음
검색 방식 전체 테이블 탐색 정렬된 인덱스 탐색
속도 O(n) O(log n)
I/O 비용 매우 높음 낮음
추가 공간 없음 필요 → 인덱스가 많아지만, 남은 DB 공간이 줄어들게 됨
INSERT/UPDATE 빠름 약간 느림 → 데이터를 자료 구조에 저장해야 하기 때문

인덱스의 한계

  1. 자주 갱신되는 컬럼엔 비효율적 : 트리 구조를 재정렬해야 하기 때문
  2. 너무 많은 인덱스는 오히려 성능 저하 : 데이터 삽입, 수정 시 모든 인덱스를 업데이트해야 하기 때문
  3. 메모리 공간 소모 : 별도의 파일 구조로 저장하기 때문(수 GB 이상 차지할 수도 있음)

⇒ 마구잡이로 인덱스를 사용하지 않고, 필요한 경우에만 인덱스를 사용해야 함!

Index 동작 원리

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 대비 수백~수천 배 빠르게 동작함

인덱스 스캔의 종류

  1. Unique Scan : 정확한 한 건의 데이터를 찾는 탐색

    인덱스가 Primary Key 또는 Unique 제약일 때 발생

    ⇒ B-Tree에서 정확히 한 경로만 따라가면 되기 때문에, 탐색 속도가 가장 빠름

    SELECT * FROM users WHERE id = 10;
    
  2. Range Scan : 일정 범위(구간)의 데이터를 연속적으로 읽는 탐색

    Between, >, <, LIKE 등의 조건에서 발생

    ⇒ B-Tree의 구조를 활용해 시작 위치를 찾고, 순서대로 이어서 읽음

    SELECT * FROM orders
    WHERE order_date BETWEEN '2025-11-01' AND '2025-11-10';
    
  3. Full Index Scan : 인덱스 전체를 순서대로 읽는 탐색

    Where 조건이 없어서 인덱스 전체를 탐색할 때 사용 → 이미 정렬되어 있어 ORDER BY 시 추가 정렬이 필요 없음

    SELECT * FROM users ORDER BY name;
    
  4. Index Condition Pushdown(ICP) : 필요한 조건을 인덱스 탐색 중에 미리 적용

    Where 조건 중 일부를 인덱스 단계에서 미리 필터링

    ⇒ 불필요한 데이터 접근을 최소화해서 디스크 I/O 감소함

    SELECT * FROM orders
    WHERE status = 'PAID' AND order_date > '2025-11-01';
    # range scan이 상대적으로 느리기 때문에, status = 'PAID'로 원하는 대상만 필터링하고, 기준대로 검색
    
  5. Table Full Scan : 인덱스를 전혀 사용하지 않는 탐색(모든 데이터를 처음부터 끝까지 확인)

<aside> 💡

옵티마이저(Optimizer) : DB가 스스로 가장 효율적인 실행 방법을 고르는 두뇌

⇒ 개발자가 인덱스만 잘 만들어두면, 어떤 인덱스를 사용할지는 DB가 자동으로 결정함

</aside>