B 세트

#4. 자동으로 생기는 인덱스들

PK·UNIQUE·FK가 만드는 숨은 인덱스

학습자료를 불러오는 중...

#4. 자동으로 생기는 인덱스들

PK·UNIQUE·FK가 만드는 숨은 인덱스

# 자동으로 생기는 인덱스들 > 📘 **학습자료 4편 / 5편** | 연결 퀴즈: [**인덱스 기초2 풀러가기**](quiz.html?set=B) > "내가 직접 만들지 않아도 InnoDB가 알아서 인덱스를 만들어주는 경우"를 다룹니다. > PK가 없을 때, UNIQUE 제약을 걸 때, FK를 걸 때 — 인덱스는 어디선가 자동으로 생겨납니다. > 📚 **이전 편**: [3편 - InnoDB의 두 가지 인덱스: 클러스터링 vs 보조] > 3편에서 InnoDB의 클러스터링 인덱스와 보조 인덱스의 구조를 다뤘습니다. 이번 편은 그 위에서 시작합니다. ## 이 자료를 다 읽으면 알게 되는 것 - PK도 NOT NULL+UNIQUE 컬럼도 없을 때, InnoDB가 만드는 **숨겨진 클러스터링 인덱스** - `UNIQUE` 제약을 걸면 **자동으로 생성되는 인덱스** - `FOREIGN KEY` 제약을 걸면 **자동으로 생성되는 인덱스** (그리고 다른 RDBMS와의 차이) - 인덱스를 직접 안 만든 테이블도 사실 인덱스가 **여러 개** 존재한다는 것 --- ## 📑 목차 - [1. PK가 없으면 InnoDB는 무엇을 할까](#1-pk가-없으면-innodb는-무엇을-할까) - [2. GEN_CLUST_INDEX: 숨겨진 클러스터링 인덱스](#2-gen_clust_index-숨겨진-클러스터링-인덱스) - [3. 제약조건이 만드는 자동 인덱스](#3-제약조건이-만드는-자동-인덱스) - [4. 실습: 자동 인덱스 개수 세보기](#4-실습-자동-인덱스-개수-세보기) - [핵심 요약](#핵심-요약) - [다음 편 예고](#다음-편-예고) --- ## 1. PK가 없으면 InnoDB는 무엇을 할까 3편에서 클러스터링 인덱스 선정 우선순위를 다뤘습니다. 다시 한번 정리하면: 1. 사용자가 명시적으로 지정한 **PRIMARY KEY** 2. **NOT NULL + UNIQUE** 조건을 모두 만족하는 첫 번째 컬럼 3. 그것도 없으면, InnoDB가 내부적으로 **숨겨진 Row ID**를 만들어 사용 3편에서는 1, 2번 케이스까지만 봤습니다. 이번 편은 **3번 케이스**부터 시작합니다. 다음과 같은 테이블이 있다고 해봅시다. 게시글 본문만 저장하는 단순한 테이블입니다. ```sql CREATE TABLE articles ( title VARCHAR(200), content TEXT ); ``` - PK 없음 - NOT NULL + UNIQUE인 컬럼도 없음 이 테이블의 **클러스터링 인덱스 키는 무엇일까요?** 직관적으로는 "그럼 클러스터링 인덱스가 없는 거 아니야?"라고 생각할 수 있습니다. 하지만 InnoDB의 모든 테이블은 **반드시 클러스터링 인덱스를 가져야** 합니다. 데이터의 물리적 저장 자체가 클러스터링 인덱스 위에 올라가는 구조이기 때문입니다. 그래서 InnoDB는 사용자가 키를 안 정해주면 **스스로 만듭니다.** --- ## 2. GEN_CLUST_INDEX: 숨겨진 클러스터링 인덱스 InnoDB는 1, 2번 후보가 모두 없을 때 **숨겨진 6바이트 Row ID** 컬럼을 내부적으로 추가하고, 이걸 클러스터링 인덱스 키로 씁니다. 이 자동 생성 인덱스의 이름이 **`GEN_CLUST_INDEX`** 입니다. - 이름이 정해져 있음: `GEN_CLUST_INDEX` (Generated Clustered Index의 줄임) - 키 컬럼은 사용자가 볼 수 없음 (숨겨짐) - 새 행이 들어올 때마다 단조 증가하는 Row ID가 자동 부여됨 **실습으로 확인해봅시다.** ```sql CREATE TABLE articles ( title VARCHAR(200), content TEXT ); SELECT * FROM INFORMATION_SCHEMA.INNODB_INDEXES; ``` ``` +----------+-----------------+----------+------+----------+---------+-------+ | INDEX_ID | NAME | TABLE_ID | TYPE | N_FIELDS | PAGE_NO | SPACE | +----------+-----------------+----------+------+----------+---------+-------+ | 163 | GEN_CLUST_INDEX | 1071 | 1 | 5 | 4 | 5 | +----------+-----------------+----------+------+----------+---------+-------+ ``` 이름이 `GEN_CLUST_INDEX`로 표시됩니다. **사용자가 만든 적 없는 인덱스가 왜인지 모르게 존재**합니다. 여기서 `TYPE = 1`을 주목해주세요. 3편에서 봤던 `TYPE = 3` (사용자 지정 클러스터링 인덱스)과 다른 값입니다. <details> <summary>📎 INNODB_INDEXES의 TYPE 값 의미 (참고)</summary> ``` 0 = 일반 보조 인덱스 (nonunique secondary index) 1 = 자동 생성된 클러스터링 인덱스 (GEN_CLUST_INDEX) ← 이번 케이스 2 = 유니크 보조 인덱스 (unique nonclustered) 3 = 사용자 지정 클러스터링 인덱스 32 = 풀텍스트 인덱스 64 = 공간 인덱스 128 = 가상 컬럼 보조 인덱스 ``` `TYPE = 1`과 `TYPE = 3`은 둘 다 클러스터링 인덱스지만, **누가 만들었느냐**의 차이입니다. </details> > 💡 **그래서 PK는 항상 명시하는 게 좋다** > InnoDB는 어떻게든 클러스터링 인덱스를 만들기 때문에, PK가 없어도 테이블은 동작합니다. 하지만: > - 숨겨진 Row ID는 단조 증가만 할 뿐 의미가 없는 값입니다 > - 모든 보조 인덱스는 리프에 클러스터링 키를 들고 있어야 하는데(3편 참고), 그 키가 6바이트 Row ID가 됩니다 > - 의도치 않은 동작이 생길 수 있습니다 (예: 복제 환경에서 Row ID가 노드별로 다를 수 있음) > > 실무에서는 항상 명시적으로 PK를 지정하는 것을 권장합니다. --- ## 3. 제약조건이 만드는 자동 인덱스 GEN_CLUST_INDEX 외에도 InnoDB(정확히는 MySQL)는 **제약조건을 걸면 인덱스를 자동으로** 만들어줍니다. 두 가지 케이스를 봅시다. ### 3-1. UNIQUE 제약 → 자동 유니크 인덱스 ```sql CREATE TABLE posts ( post_id INT PRIMARY KEY, slug VARCHAR(100) UNIQUE ); ``` `slug` 컬럼에 `UNIQUE` 제약을 걸면 (slug는 URL에 쓰이는 고유 식별자입니다 — 예: `my-first-post`), MySQL은 **자동으로 유니크 인덱스를 생성**합니다. 이건 사실 **필수에 가깝습니다.** UNIQUE 제약은 "이 컬럼의 값은 중복되면 안 된다"는 약속인데, 새 값이 들어올 때마다 중복인지 확인하려면 빠른 조회 수단이 필요합니다. 인덱스가 없으면 매 INSERT마다 **테이블 풀 스캔**으로 중복을 검사해야 하니까요. ### 3-2. FOREIGN KEY 제약 → 자동 인덱스 ```sql CREATE TABLE posts ( post_id INT PRIMARY KEY, author_id INT, FOREIGN KEY (author_id) REFERENCES users(user_id) ); ``` `author_id`에 외래키 제약을 걸면, MySQL은 **`author_id` 컬럼에도 자동으로 인덱스를 생성**합니다. 이유는 비슷합니다. FK는 부모 테이블의 행을 삭제·변경할 때 **자식 테이블에서 참조 무결성을 검사**해야 하는데, 인덱스가 없으면 매번 자식 테이블 풀 스캔이 일어나서 성능이 망가지기 때문입니다. > ⚠️ **MySQL 한정 동작입니다** > "FK 걸면 자동으로 인덱스 생성"은 **MySQL의 동작**이지, SQL 표준이나 모든 RDBMS의 공통 동작이 아닙니다. > - **PostgreSQL**: FK를 걸어도 인덱스를 자동 생성하지 **않습니다.** 직접 만들어야 합니다. > - **Oracle**: 마찬가지로 자동 생성되지 않습니다. > > 다른 DB로 이직하거나 작업할 때 헷갈리지 마세요. 신입 개발자가 잘 빠지는 함정입니다. > 💡 여기까지 읽었다면 자동 인덱스의 개념을 충분히 이해한 것입니다. [**퀴즈로 확인해보세요!**](quiz.html?set=B) --- ## 4. 실습: 자동 인덱스 개수 세보기 이제 3-1, 3-2에서 부분적으로 봤던 `posts` 테이블을 모두 합쳐봅시다. 이 테이블에 인덱스가 **몇 개 있을지** 추측해봅시다. ```sql CREATE TABLE posts ( post_id INT PRIMARY KEY, slug VARCHAR(100) UNIQUE, author_id INT, FOREIGN KEY (author_id) REFERENCES users(user_id) ); ``` - `post_id`: 게시글의 PK - `slug`: URL용 고유 식별 문자열 (3-1에서 본 UNIQUE) - `author_id`: 글쓴이를 가리키는 외래키 (3-2에서 본 FK) CREATE INDEX 같은 명시적인 인덱스 생성 구문은 **단 하나도** 없습니다. 그런데도 인덱스는 **3개**가 생깁니다. 1. **PRIMARY KEY (post_id)** → 클러스터링 인덱스 2. **UNIQUE (slug)** → 유니크 인덱스 자동 생성 3. **FOREIGN KEY (author_id)** → 일반 인덱스 자동 생성 확인 쿼리: ```sql SELECT DISTINCT index_name FROM information_schema.statistics WHERE table_name = 'posts'; ``` **결과:** | index_name | |------------| | PRIMARY | | slug | | author_id | > 💡 **"인덱스를 신중하게"라는 1편의 조언과 합쳐 생각해보면** > 1편에서 "인덱스가 많을수록 INSERT/UPDATE/DELETE가 느려진다"고 했습니다. > 그런데 우리가 모르는 사이에도 PK·UNIQUE·FK가 인덱스를 자동으로 만들고 있다면? > > 결론: **테이블을 설계할 때 자신도 모르게 인덱스를 늘리고 있을 수 있다는 것을 인지**하고 있어야 합니다. > 특히 정규화 과정에서 FK가 늘어날수록 자동 인덱스도 함께 늘어나니까요. --- ## 핵심 요약 이번 편에서 꼭 가져가야 할 한 가지: > 🎯 **인덱스는 명시적으로만 생기는 게 아니다** > PK가 없어도 InnoDB가 `GEN_CLUST_INDEX`를 만들고, `UNIQUE`/`FOREIGN KEY` 제약조건이 또 자동 인덱스를 추가합니다. > 테이블의 실제 인덱스 개수는 종종 우리 예상보다 많습니다. 체크리스트: - [x] PK도 NOT NULL+UNIQUE 컬럼도 없을 때, InnoDB가 **GEN_CLUST_INDEX**를 만든다는 것을 안다 - [x] `UNIQUE` 제약이 자동으로 유니크 인덱스를 만드는 **이유**를 설명할 수 있다 (중복 검사 필요) - [ ] `FOREIGN KEY` 자동 인덱스가 **MySQL 한정** 동작이며, PostgreSQL/Oracle은 그렇지 않다는 것을 안다 - [ ] 제약조건 3개가 걸린 테이블의 자동 인덱스 개수를 셀 수 있다 > 📝 체크리스트를 다 채울 자신이 있다면? [**인덱스 기초2 퀴즈 도전하기**](quiz.html?set=B) --- ## 다음 편 예고 이번 편에서 인덱스가 어떻게 "생겨나는지"를 봤습니다. 다음 편에서는 인덱스가 어떻게 "변경되는지"를 다룹니다. > "INSERT 한 번 하면, 모든 인덱스가 그 즉시 갱신될까?" > "사실 그렇지 않습니다. **PK는 즉시 반영, 일반 보조 인덱스는 지연 반영**됩니다." InnoDB의 핵심 최적화 기법인 **Change Buffer**, 그리고 트랜잭션의 동시성을 위해 삭제마저 "논리적으로만" 일어나는 **MVCC**까지 — 인덱스 동작의 깊은 곳을 들여다봅니다. --- ## Reference - MySQL 공식 문서: [InnoDB Index Types](https://dev.mysql.com/doc/refman/8.4/en/innodb-index-types.html) - MySQL 공식 문서: [INNODB_INDEXES Table](https://dev.mysql.com/doc/refman/8.4/en/information-schema-innodb-indexes-table.html) - MySQL 공식 문서: [CREATE TABLE](https://dev.mysql.com/doc/refman/8.4/en/create-table.html) - MySQL 공식 문서: [FOREIGN KEY Constraints](https://dev.mysql.com/doc/refman/8.4/en/create-table-foreign-keys.html)

이 세트 퀴즈 풀기 | 홈으로 돌아가기