[MySQL] Cluster Index와 Non-Cluster Index의 차이

한강섭·2025년 11월 24일

학습 & 숙제

목록 보기
104/104
post-thumbnail

데이터베이스 성능 튜닝을 이야기할 때 가장 먼저 등장하는 개념이 인덱스다. 그중에서도 Cluster Index(클러스터 인덱스)Non-Cluster Index(논클러스터 인덱스)는 구조적으로 큰 차이를 가지며, 성능에 영향을 미치는 핵심 요소다. 이 글에서는 두 인덱스가 무엇이며, 어떤 상황에서 어떤 인덱스를 사용해야 하는지 알아보자.

주의: 이 글은 MySQL InnoDB 스토리지 엔진 기준으로 작성되었습니다. SQL Server, PostgreSQL 등 다른 DBMS는 인덱스 구조가 다를 수 있습니다.


Cluster Index란 무엇인가?

Cluster Index는 테이블의 실제 레코드가 인덱스 순서대로 저장되는 구조를 말한다.
쉽게 말해, 클러스터 인덱스는 "책의 본문 페이지가 목차의 순서대로 정렬되어 있는 형태"와 비슷하다.

대표 특징

  • 테이블 자체가 인덱스 구조(B+Tree)로 저장됨
  • 오직 하나만 생성 가능 (MySQL InnoDB 기준)
  • 기본적으로 PK(Primary Key)가 클러스터 인덱스로 지정됨
  • 데이터를 PK 순으로 정렬하여 저장하기 때문에 범위 조회에 매우 빠름

PK가 없다면?

InnoDB는 다음 우선순위로 클러스터 인덱스를 결정한다:
1. PRIMARY KEY가 있으면 → PK를 클러스터 인덱스로 사용
2. PK가 없고 NOT NULL UNIQUE 인덱스가 있으면 → 첫 번째 UNIQUE 인덱스 사용
3. 둘 다 없으면 → 내부적으로 숨겨진 6바이트 row ID를 자동 생성

따라서 명시적으로 PK를 정의하는 것이 성능과 관리 측면에서 유리하다.

장점

  • PK 기반 조회가 매우 빠르다
    (예: BETWEEN, RANGE SCAN)
  • 데이터 페이지가 정렬되어 있어 순차 접근 비용이 낮음
  • 범위 검색 시 연속된 데이터 블록을 읽을 수 있어 I/O 효율이 높음

단점

  • INSERT 시 정렬을 유지해야 하므로 페이지 분할(Page Split) 발생 가능
    • 새 레코드가 중간에 삽입되면 기존 페이지를 나누고 재배치해야 함
    • UUID 같은 랜덤 값을 PK로 사용하면 페이지 분할이 빈번하게 발생
    • AUTO_INCREMENT 같은 순차적 값을 권장하는 이유
  • PK 변경이 자주 발생하면 성능 저하
  • PK 크기가 커지면 전체 인덱스 크기 증가
    → 튜닝 시 PK는 가급적 짧고 변경되지 않는 값 사용

Non-Cluster Index란 무엇인가?

Non-Cluster Index는 인덱스와 실제 데이터가 분리된 구조다.
책 뒤의 색인(index)처럼, 인덱스는 따로 저장되고 그 인덱스가 가리키는 주소로 실제 데이터에 접근하는 방식이다.

대표 특징

  • 데이터 파일과 인덱스 파일이 분리되어 있음
  • 여러 개 생성 가능
  • InnoDB의 특징: 보조 인덱스(Secondary Index)에는 PK 값이 포함되어 있음
    • 다른 스토리지 엔진(예: MyISAM)은 실제 레코드의 물리적 주소를 저장
  • 실제 데이터 접근은 (보조 인덱스 → PK 조회 → 실제 데이터) 순서로 진행됨
  • 이 과정을 "테이블 랜덤 액세스" 또는 "테이블 룩업(Table Lookup)"이라고 부른다

장점

  • PK가 아닌 컬럼으로도 빠르게 검색 가능
  • 다양한 조회 조건을 최적화할 수 있음
  • 여러 개 만들 수 있어 범용적

단점

  • 클러스터 인덱스보다 한 단계 더 거침 (추가 I/O 발생)
  • 선택도가 낮은 컬럼에 인덱스를 만들면 오히려 느려짐
  • 너무 많은 인덱스를 만들면 INSERT/UPDATE/DELETE 비용 증가

두 인덱스 구조 비교

구분Cluster IndexNon-Cluster Index
저장 구조테이블 자체가 B+Tree별도 인덱스 B+Tree
데이터 정렬PK 기준으로 실제 데이터 정렬인덱스만 정렬되고 실제 데이터는 정렬되지 않음
개수테이블당 1개여러 개 생성 가능
조회 방식바로 데이터 접근PK 검색 후 데이터 접근(테이블 랜덤 액세스)
장점범위 조회 매우 빠름다양한 컬럼 검색 최적화
단점INSERT 시 페이지 분할 발생 가능추가 I/O로 상대적으로 느림

중요한 점

1. PK는 클러스터 인덱스로서 매우 중요한 역할을 한다

PK가 너무 길면 → 모든 보조 인덱스에 PK가 포함되므로 전체 인덱스 크기가 커진다.
짧고 고유하며, 변경되지 않는 PK를 선호하는 이유다.

-- 나쁜 예: VARCHAR(100)을 PK로 사용
CREATE TABLE users (
    email VARCHAR(100) PRIMARY KEY,  -- 모든 인덱스에 100바이트 추가
    name VARCHAR(50)
);

-- 좋은 예: AUTO_INCREMENT를 PK로 사용
CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,  -- 8바이트만 추가
    email VARCHAR(100) UNIQUE,
    name VARCHAR(50)
);

2. 보조 인덱스는 선택도가 높은 컬럼에 걸자

선택도(Selectivity): 전체 행 대비 고유 값의 비율

  • 선택도가 높다 = 고유한 값이 많다 = 인덱스 효과가 좋다
  • 선택도가 낮다 = 중복된 값이 많다 = 인덱스 효과가 떨어진다
-- 나쁜 예: 선택도가 낮은 컬럼
CREATE INDEX idx_gender ON users(gender);  -- 'M', 'F' 2개 값만 존재

-- 좋은 예: 선택도가 높은 컬럼
CREATE INDEX idx_email ON users(email);  -- 거의 모든 값이 고유함

3. 커버링 인덱스로 테이블 랜덤 액세스를 최소화하자

커버링 인덱스는 쿼리에 필요한 모든 컬럼을 인덱스에 포함시켜, 테이블에 직접 접근하지 않고 인덱스만으로 결과를 반환하는 기법이다.

-- 인덱스 생성
CREATE INDEX idx_user_name_age ON users(name, age);

-- 커버링 인덱스 활용 (Using Index)
SELECT name, age FROM users WHERE name = 'Kim';
-- → 인덱스만 읽고 테이블 접근 없음

-- 커버링되지 않는 경우
SELECT name, age, address FROM users WHERE name = 'Kim';
-- → address는 인덱스에 없으므로 테이블 랜덤 액세스 발생

실행 계획에서 "Using Index"가 표시되면 커버링 인덱스가 적용된 것이다.

4. WHERE, JOIN, ORDER BY 조건이 자주 쓰인다면 인덱스 생성 고려

특히 JOIN에 쓰이는 FK 컬럼은 반드시 인덱스가 필요하다.

-- orders 테이블의 user_id에 인덱스가 없으면 JOIN 성능 저하
SELECT u.name, o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active';

-- FK 컬럼에 인덱스 생성
CREATE INDEX idx_orders_user_id ON orders(user_id);

결론

Cluster Index와 Non-Cluster Index는 단순한 인덱스의 종류가 아니라,
테이블의 저장 방식과 성능 전체에 큰 영향을 주는 핵심 구조다.

  • 빠른 범위 조회가 필요하다면 → Cluster Index(PK) 활용
  • 다양한 조회 조건 최적화 → Non-Cluster Index 활용
  • PK를 신중하게 선택해야 전체 성능이 좋아짐

인덱스 설계는 결국 트레이드오프다. 조회 성능을 높이면 쓰기 성능이 떨어지고, 인덱스를 많이 만들면 저장 공간과 유지보수 비용이 증가한다. 실제 서비스의 쿼리 패턴을 분석하고, 실행 계획을 확인하며 최적의 지점을 찾아가는 것이 데이터베이스 튜닝의 핵심이다.

profile
기록하고 공유하는 개발자

0개의 댓글