지난 글에서는
DB가 데이터를 찾는 기본 방식과 B+Tree 구조를 정리했다.
핵심은 이것이었다.
인덱스는 DB가 테이블 전체를 읽지 않기 위해 사용하는 정렬된 자료구조다.
이번 글에서는 가장 기본적인 인덱스인
단일 인덱스를 살펴본다.
단일 인덱스는 말 그대로
하나의 컬럼을 기준으로 만든 인덱스다.
1. 단일 인덱스란?
예를 들어 users 테이블이 있다.
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100),
age INT,
created_at DATETIME
);
여기서 email로 자주 조회한다면
다음과 같이 인덱스를 만들 수 있다.
CREATE INDEX idx_users_email
ON users(email);
이 인덱스는 email 컬럼 하나를 기준으로 만들어진다.
개념적으로 보면 이런 구조다.
email 값 row 위치
--------------------------------------
a@example.com row_id=10
b@example.com row_id=25
chaaany@example.com row_id=31
d@example.com row_id=7
중요한 점은
이 값들이 email 기준으로 정렬되어 있다는 것이다.
2. 등호 검색
다음 쿼리를 보자.
SELECT *
FROM users
WHERE email = 'chaaany@example.com';
email 인덱스가 없다면
DB는 테이블을 처음부터 끝까지 읽을 수 있다.
하지만 idx_users_email 인덱스가 있다면
DB는 인덱스에서 먼저 값을 찾는다.
1. idx_users_email 인덱스 탐색
2. chaaany@example.com 위치 확인
3. 연결된 row_id 확인
4. 실제 users 테이블에서 row 읽기
그림으로 보면 이렇다.
Query
|
v
idx_users_email
|
v
email = chaaany@example.com
|
v
row_id = 31
|
v
users 테이블의 실제 row
여기서 중요한 점이 있다.
인덱스만 보고 끝나는 경우도 있지만,
대부분은 인덱스에서 찾은 뒤 실제 테이블 row를 다시 읽는다.
왜냐하면 쿼리가 SELECT *이기 때문이다.
SELECT *
FROM users
WHERE email = 'chaaany@example.com';
*는 모든 컬럼을 달라는 뜻이다.
인덱스에는 email과 row 위치만 있으므로
나머지 컬럼을 가져오려면 테이블을 읽어야 한다.
3. 범위 검색
단일 인덱스는 범위 검색에도 잘 맞는다.
예를 들어 가입일 기준으로 조회한다고 하자.
CREATE INDEX idx_users_created_at
ON users(created_at);
그리고 이런 쿼리를 실행한다.
SELECT *
FROM users
WHERE created_at >= '2026-04-01'
AND created_at < '2026-05-01';
created_at 인덱스는 날짜 순서로 정렬되어 있다.
DB는 먼저 2026-04-01 위치를 찾는다.
그 다음부터 2026-05-01 전까지 순서대로 읽는다.
2026-03-30
2026-03-31
2026-04-01 <- 여기부터 읽기 시작
2026-04-02
2026-04-03
...
2026-04-30
2026-05-01 <- 여기서 중단
이 방식이 가능한 이유는
인덱스가 정렬되어 있기 때문이다.
4. ORDER BY에도 사용할 수 있다
인덱스는 검색뿐 아니라 정렬에도 도움을 줄 수 있다.
예를 들어 이런 쿼리가 있다.
SELECT *
FROM users
ORDER BY created_at;
만약 created_at 인덱스가 없다면
DB는 데이터를 읽은 뒤 별도로 정렬해야 한다.
1. users 테이블 읽기
2. created_at 기준으로 정렬
3. 결과 반환
하지만 created_at 인덱스가 있다면
이미 정렬된 구조를 활용할 수 있다.
idx_users_created_at 순서대로 읽기
즉, 별도 정렬 비용을 줄일 수 있다.
다만 항상 그런 것은 아니다.
조회 조건, 반환 row 수, DB 옵티마이저 판단에 따라 달라질 수 있다.
5. 인덱스가 있어도 항상 빠르지는 않다
여기서 중요한 오해가 있다.
인덱스를 만들면 항상 빠르다.
아니다.
예를 들어 이런 컬럼이 있다고 하자.
status VARCHAR(20)
값은 대부분 이렇게 들어간다.
ACTIVE
INACTIVE
그리고 전체 유저의 95%가 ACTIVE라고 하자.
SELECT *
FROM users
WHERE status = 'ACTIVE';
이 경우 status 인덱스가 있어도
큰 도움이 안 될 수 있다.
왜냐하면 결과가 너무 많기 때문이다.
전체 100만 건 중 95만 건이 ACTIVE
인덱스로 찾아도 어차피 대부분의 row를 읽어야 한다.
이럴 때 DB는 이렇게 판단할 수 있다.
인덱스 타고 왔다 갔다 하느니 그냥 테이블 전체를 읽는 게 낫겠다.
그래서 인덱스가 있어도
DB가 사용하지 않을 수 있다.
6. 카디널리티
여기서 나오는 개념이 카디널리티(Cardinality)다.
카디널리티는 컬럼 값의 종류가 얼마나 다양한지를 의미한다.
예를 들어 email은 카디널리티가 높다.
a@example.com
b@example.com
c@example.com
...
대부분의 사용자가 서로 다른 email을 가진다.
반면 gender, status 같은 컬럼은 카디널리티가 낮을 수 있다.
M / F
ACTIVE / INACTIVE
카디널리티가 낮은 컬럼은
단독 인덱스로 큰 효과를 보기 어려울 수 있다.
물론 항상 그런 것은 아니다.
전체 데이터 중 아주 적은 비율만 조회한다면 의미가 있다.
예를 들어 status = 'DELETED'가 전체의 0.1%라면
인덱스가 도움이 될 수 있다.
중요한 것은 값의 종류 자체보다
조건으로 걸렀을 때 얼마나 많이 줄어드느냐다.
7. 인덱스의 비용
인덱스는 조회를 빠르게 해준다.
하지만 공짜는 아니다.
인덱스를 만들면 DB는 데이터를 변경할 때마다
인덱스도 함께 관리해야 한다.
예를 들어 이런 INSERT가 있다.
INSERT INTO users(id, email, age, created_at)
VALUES (1, 'chaaany@example.com', 30, NOW());
만약 인덱스가 3개라면
DB는 테이블만 쓰는 게 아니다.
1. users 테이블에 row 저장
2. Primary Key 인덱스 갱신
3. email 인덱스 갱신
4. created_at 인덱스 갱신
인덱스가 많을수록
쓰기 작업은 더 무거워진다.
8. UPDATE도 비용이 든다
인덱스가 걸린 컬럼을 변경하면
인덱스도 바뀌어야 한다.
UPDATE users
SET email = 'new@example.com'
WHERE id = 1;
이 경우 DB는 대략 이렇게 처리한다.
1. 기존 email 인덱스 entry 제거
2. 새로운 email 인덱스 entry 추가
3. 실제 row 변경
반대로 인덱스가 없는 컬럼을 수정하면
해당 인덱스 갱신 비용은 없다.
UPDATE users
SET name = 'chaaany'
WHERE id = 1;
name에 인덱스가 없다면
name 인덱스를 수정할 필요는 없다.
9. 단일 인덱스 정리
단일 인덱스는 가장 기본적인 인덱스다.
CREATE INDEX idx_users_email
ON users(email);
핵심은 다음과 같다.
1. 하나의 컬럼을 기준으로 정렬된 구조다.
2. 등호 검색에 사용할 수 있다.
3. 범위 검색에 사용할 수 있다.
4. ORDER BY에 도움을 줄 수 있다.
5. 카디널리티가 낮으면 효과가 작을 수 있다.
6. INSERT / UPDATE / DELETE 비용이 증가한다.
한 줄로 정리하면 이렇다.
단일 인덱스는 하나의 컬럼으로 데이터를 빨리 찾게 해주지만, 쓰기 비용과 저장공간을 함께 증가시킨다.
다음 글에서는
실무에서 훨씬 자주 고민하게 되는 복합 인덱스를 정리해본다.
'프로그래밍공부(Programming Study) > CS-데이터베이스(Database)' 카테고리의 다른 글
| 복합 인덱스와 실무 인덱스 설계 기준 (0) | 2026.05.01 |
|---|---|
| DB는 데이터를 어떻게 찾을까? - Full Scan과 B+Tree (0) | 2026.04.29 |
| CAP 정리: 일관성, 가용성, 분할내성의 상충관계 완벽 정리 (1) | 2024.12.04 |
| MongoDB 특징 및 설치/환경설정 (0) | 2023.01.03 |
| MySQL 버전별 차이 (0) | 2022.12.03 |
댓글