데이터베이스 인덱스: 중요성, 종류, 그리고 최적화 방법
데이터베이스 인덱스란?
데이터베이스 인덱스는 데이터 검색 속도를 향상시키는 데이터 구조입니다. 인덱스는 데이터베이스가 특정 행을 빠르게 찾을 수 있도록 도와주는 지도 역할을 합니다. 하지만 인덱스를 사용하면 저장 공간이 더 필요하고, 쓰기 작업이 느려지는 단점이 있습니다.
인덱스가 중요한 이유
인덱스는 관계형 데이터베이스에서 쿼리 실행 시간을 크게 줄여줍니다. 인덱스가 없으면 데이터베이스는 전체 테이블 스캔을 수행하여 각 행을 검사해야 합니다. 이 경우 시간 복잡도는 **O(N)**입니다. 하지만 인덱스를 사용하면 **O(log N)**의 시간 복잡도로 훨씬 빠르게 데이터를 검색할 수 있습니다.
예를 들어, customer 테이블에서 아래와 같은 MySQL 쿼리가 있다고 가정해봅시다:
SELECT * FROM customer WHERE first_name = 'Minsoo';
이 테이블에 100만 개의 행이 있고 first_name 컬럼에 인덱스가 없다면 데이터베이스는 모든 행을 검사해야 합니다. 하지만 first_name 컬럼에 인덱스를 설정하면 데이터베이스는 해당 인덱스를 통해 일치하는 행을 빠르게 찾을 수 있습니다.
MySQL에서 인덱스 생성 방법
인덱스는 기존 테이블에 추가할 수도 있고, 테이블을 생성할 때 지정할 수도 있습니다.
기존 테이블에 인덱스 추가하기
CREATE INDEX customer_first_name_idx ON customer (first_name);
고유 인덱스 생성하기
컬럼 조합이 고유해야 하는 경우(예: 스포츠 선수 테이블에서 팀과 등번호 조합) **고유 인덱스(Unique Index)**를 생성할 수 있습니다:
CREATE UNIQUE INDEX team_id_backnumber_idx ON player (team_id, backnumber);
이 인덱스는 같은 팀에서 동일한 등번호가 중복될 수 없도록 보장합니다.
인덱스의 종류
- B-트리 인덱스 (MySQL 기본값)
- 해시 인덱스(Hash Index)
- 전체 텍스트 인덱스(Full-Text Index)
- 공간 인덱스(Spatial Index)
대부분의 경우 B-트리 인덱스가 범위 검색과 동등 검색을 효율적으로 지원하기 때문에 가장 널리 사용됩니다.
인덱스의 작동 방식: B-트리 구조
B-트리 인덱스는 MySQL에서 가장 일반적으로 사용되는 인덱스 유형입니다. 이는 데이터를 균형 잡힌 트리 구조로 구성하여 데이터베이스가 트리를 빠르게 탐색할 수 있도록 합니다.
예시:
인덱스 테이블:
+-----------+-----------+
| 인덱스 키 | 행 ID |
+-----------+-----------+
| Minsoo | 1001 |
| Jisoo | 1002 |
+-----------+-----------+
데이터베이스는 전체 테이블을 스캔하는 대신 인덱스를 검색하여 검색 범위를 크게 줄일 수 있습니다.
커버링 인덱스(Covering Index)
커버링 인덱스는 쿼리가 필요한 모든 컬럼을 포함하는 인덱스입니다. 이 경우 데이터베이스는 테이블에 접근하지 않고 인덱스만으로 쿼리를 처리할 수 있습니다.
예시:
SELECT first_name, last_name FROM customer WHERE first_name = 'Minsoo';
(first_name, last_name)에 인덱스가 있다면, 쿼리는 테이블을 조회하지 않고 인덱스만으로 처리할 수 있습니다.
해시 인덱스(Hash Index)
해시 인덱스는 해시 테이블을 사용하여 값을 위치에 매핑합니다. 이는 O(1) 시간 복잡도로 동등 검색을 수행할 수 있지만 몇 가지 한계가 있습니다:
- 동등 비교 쿼리만 지원합니다.
- 범위 검색을 지원하지 않습니다.
- 해시 충돌이 발생할 경우 **재해싱(Rehashing)**이 필요합니다.
이러한 제한 사항으로 인해 대부분의 경우 B-트리 인덱스가 더 적합합니다.
인덱스 사용 예시
player 테이블을 고려해봅시다:
CREATE TABLE player (
id INT PRIMARY KEY,
name VARCHAR(50),
team_id INT,
backnumber INT
);
예시 1: 이름으로 검색
SELECT * FROM player WHERE name = 'Sonny';
해결 방법:
CREATE INDEX player_name_idx ON player (name);
예시 2: 팀과 등번호로 검색
SELECT * FROM player WHERE team_id = 105 AND backnumber = 7;
해결 방법:
CREATE UNIQUE INDEX team_id_backnumber_idx ON player (team_id, backnumber);
인덱스 사용 시 베스트 프랙티스
- WHERE, JOIN, ORDER BY, GROUP BY 절에 자주 사용되는 컬럼에 인덱스를 생성합니다.
- 불필요한 인덱스를 생성하지 않습니다. 인덱스가 많으면 쓰기 작업(INSERT, UPDATE, DELETE)이 느려집니다.
- 복합 인덱스(Composite Index)를 활용합니다. 여러 컬럼을 동시에 필터링하는 쿼리에 유용합니다.
- EXPLAIN 명령어로 쿼리 실행 계획을 분석합니다.
- 사용하지 않는 인덱스를 제거합니다.
인덱스의 단점
- 저장 공간 사용 증가: 각 인덱스는 추가 디스크 공간을 차지합니다.
- 쓰기 작업이 느려짐: 행을 삽입, 업데이트, 삭제할 때마다 관련된 모든 인덱스를 업데이트해야 합니다.
- 인덱스 유지 관리 필요: 최적의 성능을 유지하려면 정기적으로 인덱스를 점검해야 합니다.
옵티마이저와 인덱스 선택
MySQL의 쿼리 옵티마이저는 각 쿼리에 대해 가장 적합한 인덱스를 자동으로 선택합니다. 그러나 경우에 따라 서브옵티멀 인덱스를 선택할 수 있습니다. 이때 개발자는 다음과 같은 방법으로 인덱스 사용을 조정할 수 있습니다:
- 인덱스 힌트를 사용하여 특정 인덱스를 지정합니다.
- EXPLAIN을 사용하여 쿼리 실행 계획을 분석합니다.
인덱스 힌트 예시:
SELECT * FROM player USE INDEX (player_name_idx) WHERE name = 'Sonny';
요약
인덱스는 데이터베이스 최적화의 핵심 요소입니다. 인덱스를 사용하면 쿼리 성능이 크게 향상되며, 검색 속도가 **O(N)**에서 **O(log N)**로 줄어듭니다. 그러나 인덱스 수와 유형을 신중하게 관리해야 하며, 정기적으로 쿼리 성능과 인덱스 사용 상태를 분석하는 것이 중요합니다.
적절한 인덱스를 이해하고 적용함으로써 대규모 데이터셋에서도 최적의 데이터베이스 성능을 유지할 수 있습니다.