Clustered Index (클러스터링 인덱스)
- 데이터베이스 테이블의 실제 데이터가 인덱스의 정렬 순서대로 저장되는 구조입니다.
- 도서관에서 책이 "책 제목" 순으로 정리되어 있는 것과 같습니다.
- 리프 노드 = 실제 레코드
- 테이블당 하나만 만들 수 있습니다.
- 인덱스가 곧 데이터이기 때문에, 범위 조회나 정렬 쿼리에서는 매우 빠릅니다.
- 다만, 정렬 유지 비용이 있으므로 빈번한 삽입·삭제에는 부적합합니다.
Non-Clustered Index (논-클러스터링 인덱스)
- 인덱스가 실제 데이터와는 별도로 존재하며, 인덱스 항목이 데이터 위치를 가리키는 포인터를 포함하는 구조입니다.
- 리프 노드에는 인덱스 키 + 참조값(PK 또는 RowID)이 저장되며, 데이터 조회를 위해 **추가 접근(lookup)**이 필요합니다.
- 책의 목차에서 "10페이지를 보세요"에 해당하는 구조입니다.
- 테이블당 여러 개 만들 수 있어, 여러 조건을 위한 다양한 인덱스를 만들 수 있어 유연합니다.
- 다만, 실제 데이터를 찾기 위해 추가 접근(I/O)이 발생할 수 있습니다. (Index → PK → 데이터)
Clustered vs Non-Clustered 인덱스 비교
구분 | Clustered Index | Non-Clustered Index |
대상 | Primary Key / Unique + Not Null / 숨겨진 Row ID | 일반적인 보조 인덱스 |
역할 | 실제 데이터 저장 기준 | 보조 검색용 인덱스 |
제한 | 테이블당 1개만 가능 | 여러 개 생성 가능 |
리프 노드 | 실제 데이터 레코드 포함 | 인덱스 키 + Clustered Index 키 |
성능 특성 | 정렬/범위 조회에 탁월, 쓰기 성능은 낮을 수 있음 | 다양한 조건 검색 유리, 다만 I/O 비용이 발생 |
동작 방식 예시 (JPA 기준)
Entity 예시
@Entity
@Table(
name = "member",
indexes = {
@Index(name = "idx_name", columnList = "name") // Non-Clustered Index
}
)
public class Member {
@Id // → Clustered Index (InnoDB에서 PK는 기본적으로 clustered)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String email;
}
Clustered Index 사용: 기본 키 조회
Optional<Member> findById(Long id);
- findById: PK 기반 조회 → 클러스터드 인덱스 직접 접근
SELECT * FROM member WHERE id = ?;
id는 PK이며 Clustered Index
리프 노드 = 데이터 → 즉시 접근 (1회 I/O)
Lookup 불필요
Non-Clustered Index 사용: 조건 검색 + Lookup
// JPA Repository 메서드
List<Member> findByName(String name);
SELECT * FROM member WHERE name = ?;
name은 보조 인덱스 (Non-Clustered Index)
리프 노드에는 name + PK(id) 저장
1차 탐색: name 인덱스 → PK(id)
2차 탐색: id 통해 Clustered Index 재탐색
총 2회 I/O
인덱스 내부 탐색 구조
Clustered Index 구조
[ Clustered Index 구조 ]
name
┌─────┐
│ 도리 │
└─────┘
↓
┌────────────────────┐
│ id=1002, name=도리 │ ← 실제 레코드
│ id=1007, name=도리 │
└────────────────────┘
※ 리프 노드 = 실제 데이터 → 추가 탐색 없이 바로 결과 반환
Non-Clustered Index 구조
[ Non-Clustered Index (name 기준) ]
name
┌─────┐
│ 도리 │
└─────┘
↓
┌─────────────────────┐
│ 도리 → PK = 1002 │
│ 도리 → PK = 1007 │
│ 도리 → PK = 1001 │
└─────────────────────┘
※ 리프 노드에는 실제 데이터가 아닌 **PK 참조값**만 저장됨
인덱스 자료구조가 B-Tree인데, 왜 해시 인덱스를 쓰지 않을까요?
해시 인덱스가 단일 값 조회에서는 이론상 더 빠를 수 있지만, 일반적인 RDBMS에서는 B-Tree 인덱스를 주로 사용하는 이유
- 정렬되지 않음 → 해시 인덱스는 **범위 검색(BETWEEN, >, <, ORDER BY)**이 불가능
- 충돌 발생 가능성 → 해시값 충돌 시 추가 탐색 필요
- 쿼리 최적화 제한 → 옵티마이저가 사용할 수 있는 통계 정보가 부족
- 메모리 사용량 증가 → 메모리 기반으로 설계되기 때문에 대용량에는 부적합
- InnoDB에서는 기본적으로 B+ Tree 구조를 사용하고, 해시 인덱스는 MEMORY 엔진 등 특수 용도에서만 사용
참고: MySQL 공식문서: Hash Index vs B-Tree
MySQL :: MySQL 8.0 Reference Manual :: 10.3.9 Comparison of B-Tree and Hash Indexes
10.3.9 Comparison of B-Tree and Hash Indexes Understanding the B-tree and hash data structures can help predict how different queries perform on different storage engines that use these data structures in their indexes, particularly for the MEMORY storage
dev.mysql.com
결론
lustered Index는 테이블의 실제 데이터가 인덱스 키 순서대로 정렬되어 저장되는 구조로, 리프 노드에 실제 레코드가 포함되며 테이블당 하나만 존재할 수 있습니다. 반면 Non-Clustered Index는 인덱스와 데이터가 분리되어 있으며, 리프 노드에는 인덱스 키와 함께 참조 키(PK)가 저장되어 데이터를 조회하려면 추가 탐색이 필요합니다. 핵심 차이는 리프 노드에 실제 데이터가 저장되느냐(Clustered), **참조값만 저장되느냐(Non-Clustered)**이며, JPA에서는 @Id가 Clustered Index의 기준이 되고, @Index는 보조 인덱스로 생성되어 조건 검색 시 Lookup이 발생합니다. 따라서 쿼리 특성과 데이터 변경 패턴에 따라 인덱스 구조를 적절히 선택하는 것이 성능 최적화의 핵심입니다.
'CS' 카테고리의 다른 글
CS 스터디 (DB) 16 - IN 절에 들어갈 수 있는 최대 항목 수에 대해 알고 계실까요? (0) | 2025.05.20 |
---|---|
CS 스터디 (DB) 15 - 커버링 인덱스에 대해 설명해주세요. (0) | 2025.05.20 |
CS 스터디 (DB) 13 - 쿼리 실행 순서는 어떻게 될까요? (0) | 2025.05.20 |
CS 스터디 (Java) 12 - 직렬화에서 SerialVersionUID 를 선언해야하는 이유는? (1) | 2025.05.17 |
CS 스터디 (Java) 11 - 상속(Inheritance)이 위험할 수 있는 이유와 합성(composition)을 선호해야 하는 이유를 설명하시오. (0) | 2025.05.17 |