고유 인덱스란 무엇인가?
고유 인덱스(Unique Index)는 인덱스 값이 중복되지 않도록 제한하는 구조다. 예를 들어, 주민등록번호나 이메일 주소처럼 시스템 내에서 유일성이 보장되어야 하는 필드에 사용된다. 만약 시민 정보 시스템에서 주민등록번호로 이름을 조회하는 쿼리가 자주 실행된다면,
SELECT name FROM citizen WHERE id_number = '1234567890123';
이 경우 id_number 컬럼에 인덱스를 생성하는 것이 필수적이다.
주민등록번호를 기본키로 사용하는 것은 왜 비효율적인가?
기본키(PK)는 클러스터형 인덱스(Clustered Index)로 사용되며, 모든 보조 인덱스는 기본키 값을 포함한다. 주민등록번호처럼 긴 문자열을 기본키로 설정하면 저장 공간과 메모리 사용량이 크게 증가하며, 조인이나 참조 연산에서도 부담이 커진다. 따라서 별도의 자동 증가 정수형 ID를 기본키로 두고, 주민등록번호에는 고유 인덱스만 걸어주는 것이 바람직하다.
고유 인덱스 vs 일반 인덱스: 어떤 것을 선택해야 할까?
애플리케이션 레벨에서 이미 주민등록번호의 중복 삽입이 방지되고 있다면, id_number 필드에 고유 인덱스 대신 일반 인덱스(Normal Index)를 생성할 수 있다. 하지만 궁극적으로는 무결성을 데이터베이스 수준에서도 보장하기 위해 고유 인덱스를 권장한다. 그러나 성능 측면에서는 일반 인덱스가 일부 장점이 있다.
쿼리 성능 비교: 고유 인덱스와 일반 인덱스
B+ 트리 구조에서 인덱스 탐색은 다음과 같은 과정으로 진행된다:
- 루트 노드에서 시작해 리프 노드까지 내려간다.
- 리프 페이지 내에서 이진 탐색을 통해 해당 키 값을 찾는다.
예를 들어 다음 쿼리를 수행한다고 하자:
SELECT id FROM users WHERE k = 5;
- 일반 인덱스: 키 값이 5인 첫 번째 레코드를 찾은 후, 다음 레코드가 5가 아닌 지점까지 계속 스캔한다 (범위 스캔).
- 고유 인덱스: 첫 번째 일치하는 레코드를 찾으면 바로 탐색을 종료한다.
성능 차이는 거의 없다. 왜냐하면 InnoDB는 데이터를 페이지 단위(기본 16KB)로 읽기 때문에, 동일한 페이지 내에 있는 인접 레코드들을 동시에 로드하므로 추가 탐색의 오버헤드가 미미하기 때문이다.
변경 버퍼(Change Buffer): 업데이트 성능 핵심
데이터 페이지가 현재 메모리에 존재하지 않을 때, InnoDB는 변경 사항을 변경 버퍼에 기록하고 실제 페이지 갱신은 나중으로 미룬다. 이를 통해 비용이 큰 디스크 I/O를 줄일 수 있다.
변경 버퍼는 어디에 저장되는가?
변경 버퍼는 innodb_buffer_pool 내부의 일부 영역으로, 메모리뿐만 아니라 디스크에도 기록되어 시스템 충돌 시 복구가 가능하다.
머지(Merge)란 무엇인가?
변경 버퍼에 기록된 작업을 실제 데이터 페이지에 반영하는 과정을 말한다. 머지는 다음 상황에서 발생한다:
- 해당 페이지가 조회되거나 업데이트되기 위해 메모리에 로드될 때
- 백그라운드 스레드가 주기적으로 수행할 때
- MySQL 종료(shutdown) 과정 중
변경 버퍼의 이점
- 불필요한 디스크 읽기를 감소시켜 업데이트 속도를 향상시킨다.
- Buffer Pool 내 캐시 효율을 높여 메모리 자원을 더 효율적으로 사용할 수 있다.
고유 인덱스는 왜 변경 버퍼를 사용할 수 없는가?
고유 인덱스는 삽입 또는 갱신 시 반드시 중복 여부를 확인해야 한다. 이를 위해서는 관련 데이터 페이지를 반드시 메모리에 로드해야 하므로, 변경 버퍼를 통한 지연 적용이 불가능하다. 반면 일반 인덱스는 중복 검사를 생략할 수 있어 변경 버퍼 활용이 가능하다.
INSERT 연산에서의 처리 흐름 비교
새로운 레코드 (4, 400)을 삽입할 때의 동작:
| 조건 | 고유 인덱스 | 일반 인덱스 |
|---|---|---|
| 페이지가 메모리에 있음 | 바로 삽입 + 중복 체크 | 바로 삽입 |
| 페이지가 메모리에 없음 | 페이지를 디스크에서 로드 → 삽입 및 체크 | 변경 버퍼에 기록 후 종료 |
두 번째 경우에서 일반 인덱스는 디스크 I/O를 아낄 수 있으므로 업데이트 성능이 훨씬 우수하다.
실제 운영 사례: 인덱스 변경 후 성능 저하
한 DBA가 로그 시스템의 일반 인덱스를 고유 인덱스로 변경한 후, 버퍼 풀의 메모리 적중률이 99%에서 75%로 급락하고 시스템이 정체된 사례가 있었다. 원인은 대량의 INSERT 작업에서 고유 인덱스로 인해 모든 페이지를 실시간으로 로드해야 했고, 이로 인해 디스크 I/O 폭증과 버퍼 풀 압박이 발생했기 때문이다.
변경 버퍼의 효과적인 사용 조건
변경 버퍼는 다음 조건에서 가장 효과적이다:
- 쓰기 중심, 읽기 드문 시나리오: 예를 들어, 거래 내역, 접근 로그, IoT 센서 데이터 등
- 갱신 후 즉시 조회하지 않는 경우
반대로, 데이터 삽입 직후 바로 조회하는 패턴이라면 변경 버퍼는 오히려 부하를 증가시킨다. 왜냐하면 머지가 즉시 발생하여 I/O 절감 효과가 없고, 추가로 변경 버퍼 관리 오버헤드만 발생하기 때문이다. 이런 경우에는 변경 버퍼를 비활성화하는 것이 낫다.
REDO 로그 vs 변경 버퍼: 핵심 차이
둘 다 성능 최적화를 위한 메커니즘이지만 목적은 다르다:
- REDO 로그: 메모리에서 디스크로의 쓰기 I/O를 직렬화하고 순차 쓰기로 전환하여 효율화함.
- 변경 버퍼: 디스크에서 메모리로의 읽기 I/O를 회피함으로써 성능을 개선함.
충돌 시 변경 버퍼는 유실되는가?
아니다. 변경 버퍼의 변경 사항도 REDO 로그에 기록되기 때문에, 서버 다운 이후 재시작 시 복구 과정에서 다시 적용된다. 따라서 데이터 무결성이 유지된다.
최종 결론: 어떻게 선택해야 하나?
- 쿼리 성능 측면에서는 고유 인덱스와 일반 인덱스 간 실질적 차이가 거의 없다.
- 업데이트 성능 측면에서는 일반 인덱스가 변경 버퍼 활용 가능으로 인해 유리하다.
- 하지만 데이터 무결성 보장을 위해서는 고유 인덱스 사용을 우선 고려해야 한다.
- 단, 애플리케이션에서 충분히 중복 방지 로직이 보장되고, 쓰기 집약적인 환경이라면 일반 인덱스 + 변경 버퍼 조합이 더 나은 선택일 수 있다.