자바 스프링 부트 개념 정리(Index)

제이 용·2025년 12월 19일

인덱스(Index)

  • 인덱스는 데이터 조회 속도를 높이기 위한 ‘검색용 지도’이다.

인덱스 핵심 개념 정리

  • 원하는 데이터를 빠르게 조회하기 위한 자료구조

  • 테이블과는 별도로 저장됨

  • 일반적으로 B-Tree 구조 사용

  • PRIMARY KEY, UNIQUE 제약 조건 컬럼에는 자동 생성


인덱스 적용 전 / 후 차이

구분인덱스 없음인덱스 있음
검색 방식전체 테이블 탐색(Full Scan)인덱스 탐색
시간 복잡도O(n)O(log n)
I/O 비용매우 높음낮음
추가 저장 공간없음필요
INSERT/UPDATE 성능빠름약간 느려짐

조회 성능은 크게 향상되지만, 쓰기 성능과 공간 비용은 증가

인덱스 실습

  • 사용자 테이블에 대량 데이터(천만 건) 생성

  • 인덱스 미적용 상태에서 조회 시 전체 테이블 스캔 발생

  • name 컬럼에 인덱스 생성 후
    → 동일 조건 조회가 인덱스 탐색으로 전환되어 속도 개선

CREATE INDEX idx_user_name ON user(name);

정리

인덱스는 조회 성능을 비약적으로 향상시키지만,

무분별하게 사용하면 쓰기 성능 저하와 공간 낭비를 유발한다.

자주 조회되는 컬럼에만 전략적으로 적용하는 것이 중요✨


인덱스는 단순한 정렬이 아님

인덱스는 단순한 가나다순 정렬이 아니라,

데이터를 빠르게 찾기 위한 B-Tree 기반의 탐색 지도이다.

  • 단순 정렬: 처음부터 끝까지 순차 탐색

  • B-Tree 인덱스: 분기(branch)를 따라 필요한 범위만 탐색

  • 탐색 비용은 트리 높이에 비례 → O(log n)


B-Tree 인덱스 동작 원리

  • 루트 → 중간 노드 → 리프 노드 순으로 비교

  • 불필요한 데이터는 건너뛰고 정확한 경로만 탐색

  • Full Scan 대비 수백~수천 배 빠름

    • WHERE id = 60 같은 조회도

몇 단계 비교만으로 실제 레코드 위치 도달


인덱스 스캔 종류 요약

Unique Scan

  • PK / UNIQUE 컬럼 조회

  • 정확히 한 건 탐색

  • 가장 빠름

  • SELECT * FROM users WHERE id = 10;

Range Scan

  • 범위 조건 조회

  • 시작 위치 탐색 후 연속 읽기

  • 정렬된 인덱스 덕분에 ORDER BY 효율적

WHERE age BETWEEN 20 AND 30
WHERE name LIKE 'abc%'

Full Index Scan

  • 인덱스 전체를 순서대로 탐색

  • WHERE 조건은 없지만 ORDER BY 최적화 가능

SELECT * FROM users ORDER BY name;

Index Condition Pushdown (ICP)

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

  • 테이블 접근 횟수 감소 → 디스크 I/O 절약

WHERE status = 'PAID' AND order_date > '2025-11-01'

Table Full Scan

  • 인덱스를 전혀 사용하지 못하는 경우

  • 모든 행을 직접 비교 → 가장 느림

WHERE city = 'Seoul' -- 인덱스 없음

옵티마이저 역할

  • DB가 가장 효율적인 실행 계획을 자동 선택

  • 개발자는 인덱스 설계만 신경 쓰면 됨

  • 실제 사용 여부는 옵티마이저 판단

  • 인덱스의 한계

    • 잦은 변경 컬럼 : 삽입/삭제 시 트리 재정렬 비용
    • 과도한 인덱스 쓰기 : 성능 저하
    • 메모리/디스크 사용 : 인덱스도 별도 저장 구조

결론

  • 인덱스 유무에 따라 실행 계획(EXPLAIN)이 완전히 달라짐

  • PK → Unique Scan

  • 범위 조건 → Range Scan

  • 인덱스 없음 → Full Table Scan

요약

인덱스는 단순 정렬이 아닌 B-Tree 기반 탐색 구조이며,

조회 성능을 극적으로 개선하지만 쓰기 비용과 공간을 고려해 설계해야 한다.


복합 인덱스(Composite Index) 정의

복합 인덱스는 여러 컬럼을 하나의 인덱스로 묶어,

자주 함께 사용되는 조건을 빠르게 탐색하기 위한 인덱스다.


왜 복합 인덱스가 필요한가?

  • 단일 인덱스는 하나의 컬럼 기준 정렬만 가능

  • 실무에서는 보통 여러 조건을 함께 조회

  • 예: start_date + end_date

이 경우 단일 인덱스 여러 개보다 복합 인덱스 하나가 훨씬 효율적


복합 인덱스의 핵심 규칙

  • 왼쪽 접두어 규칙 (Leftmost Prefix Rule)

  • 복합 인덱스는 왼쪽(선두) 컬럼부터 순서대로만 사용된다.

(start_date, end_date)
  • start_date 기준으로 먼저 정렬

  • 같은 start_date 내에서 end_date 정렬


인덱스 사용 가능 여부 요약

  • 복합 인덱스 (a, b, c) 기준
WHERE 조건인덱스 사용
a
a, b
a, b, c
b
b, c

선두 컬럼(a)이 빠지면 인덱스 사용 불가


포인트

  • (start_date, end_date) 인덱스

    • start_date + end_date 조건 → ✅ 인덱스 사용

    • end_date 단독 조건 → ❌ Full Table Scan

  • 인덱스 순서를 바꾸면 사용은 되더라도

  • 쿼리 패턴과 맞지 않으면 비효율적

WHERE 조건과 인덱스 순서는 일치시키는 게 가장 안전


복합 인덱스가 특히 유용한 경우

  • 항상 함께 조회되는 컬럼들

  • WHERE + ORDER BY를 동시에 만족해야 할 때

  • 페이징 쿼리 성능이 중요한 경우

  • 대표 예시

    • (category_id, price) → 상품 목록 + 정렬

    • (user_id, order_date) → 사용자별 기간 조회

    • (board_id, created_at) → 게시판 최신글

    • (region, district) → 지역 기반 검색


단일 인덱스 여러 개 vs 복합 인덱스

  • 단일 인덱스 여러 개: 옵티마이저 조합 필요, 효율 낮음

  • 복합 인덱스 하나: 한 번의 탐색으로 범위 축소

두 컬럼 이상이 항상 함께 쓰이면 복합 인덱스가 정답

요약

복합 인덱스는 자주 함께 조회되는 컬럼을 하나의 B-Tree로 묶어,

왼쪽 접두어 규칙을 기반으로 조회·정렬 성능을 동시에 최적화한다.


목표

  • JOIN + GROUP BY 쿼리에서 인덱스 유무에 따른 실행 계획과 성능 차이를 확인한다.

  • 테이블 구조 요약

  • users

    • id (PK)

    • username (UNIQUE → 자동 인덱스)

  • posts

    • id (PK)

    • user_id (FK 성격, 인덱스 없음)

  • comments

    • id (PK)

    • post_id (FK 성격, 인덱스 없음)

JOIN 대상 컬럼(posts.user_id, comments.post_id)에 인덱스가 없는 상태

실행 쿼리

  • 특정 username의 게시글 목록 조회

  • 게시글별 댓글 수 집계

  • users → posts → comments LEFT JOIN + GROUP BY post.id

인덱스 없이 실행했을 때

  • 문제점

    • posts, comments 모두 Full Table Scan

    • JOIN 컬럼에 인덱스가 없어 모든 행 탐색

    • 전체 스캔량: 11 × 8 = 88행

데이터가 커질수록 기하급수적으로 느려짐


EXPLAIN에서 꼭 볼 3가지

항목의미판단 기준
type탐색 방식ALL ❌ / ref, const
key사용 인덱스NULL
rows예상 읽기 수작을수록 좋음

JOIN 컬럼에 인덱스 추가

인덱스 추가 후 실행 계획

  • 개선 효과

    • Full Scan 제거

    • JOIN 대상만 정확히 탐색

    • 전체 스캔량: 약 3~4행 수준

    • 실행 시간: 100~200ms → 1~3ms

요약

JOIN 성능의 핵심은 WHERE 조건뿐 아니라

JOIN 컬럼(user_id, post_id)에 인덱스가 있는가이다.

EXPLAIN에서 ALL + NULL + rows 많음이면 → 인덱스 설계부터 의심하자.


공부하자...이제 쿼리 성능을 개선하는 법도 알게 되었다..! ( •̀ ω •́ )y

0개의 댓글