😅 필자가 개인적으로 공부하고 남기는 글이라 정보가 미흡할 수 있습니다.
잘못된 내용이나 더 좋은 관점이 있다면 댓글로 알려주시면 정말 감사히 받겠습니다.
보통 실무에서 테이블을 설계할 때 Primary Key(PK) 없이 테이블을 만들 일은 거의 없다.
"각 행을 유니크하게 식별할 수 있어야 한다"는 기본 원칙 때문이기도 하고, ORM/JPA 같은 도구를 쓰면 사실상 PK가 없으면 운영이 힘들기 때문이다.
그런데 MySQL(InnoDB)은 PK가 없는 테이블 생성을 허용한다.
필자는 처음에 이게 이해가 잘 안 됐다.
굳이 왜 허용할까? 라는 생각이 들었다. 🤔
그러다 문서를 보다가 "아… 이래서구나" 싶은 지점이 있었다.
InnoDB는 PK가 없더라도 클러스터드 인덱스(Clustered Index) 를 내부적으로 만든다.
즉, InnoDB 입장에서는 "PK 없는 테이블"도 결국 클러스터드 인덱스 기반 구조로 저장되도록 설계되어 있었던 것이다.
클러스터드 인덱스는 테이블의 데이터(레코드)가 특정 키 순서로 정렬되어 저장되는 구조이다.
그래서 클러스터드 인덱스의 키(= 클러스터링 키)를 기준으로 레코드가 정렬되어 저장된다.
InnoDB에서는 보통 이 클러스터링 키가 Primary Key가 된다.
그래서 흔히 이런 말을 한다.
"InnoDB는 PK를 기준으로 클러스터드 인덱스가 만들어진다."
그리고 필자는 여기서 한 번 오해를 했다.
"PK가 있어야만 클러스터드 인덱스가 존재하는구나?"
근데 실제로는 반대에 가깝다.
즉 "클러스터드 인덱스가 PK 때문에 생긴다"가 아니라,
"InnoDB는 클러스터드 인덱스가 기본이고, PK가 있으면 그걸 키로 쓴다"에 더 가깝다.
PK가 없는 테이블이라고 해서 내부 동작이 똑같지는 않다.
InnoDB가 클러스터링 키를 고르는 우선순위가 있기 때문이다.
필자가 정리한 PK 없는 테이블은 크게 두 가지로 나뉜다.
이렇게 나누는 이유는 단순하다.
InnoDB가 "클러스터드 인덱스 키로 쓸 만한 후보"를 찾는 방식이 달라지기 때문이다.
이 경우 InnoDB는 테이블에 정의된 첫 번째 Unique 인덱스(NOT NULL 조건 포함)를
클러스터링 키로 선택한다.
즉, PK를 안 만들었어도 UK가 사실상 PK 역할을 하게 되는 구조다.
주의: "아무 UK나" 되는 게 아니라, 클러스터링 키로 쓰려면 NOT NULL 조건이 중요한 포인트로 알려져 있다. (NULL 허용이면 유니크 보장이 애매해짐)
식별 가능한 컬럼이 없는데, InnoDB는 대체 뭘 기준으로 클러스터드 인덱스를 결정할까.
InnoDB는 이때 숨겨진 컬럼(hidden column) 을 하나 사용한다.
row_id 성격의 값이 있고그래서 PK/UK가 없으면 InnoDB는 이 row_id를 기반으로
GEN_CLUST_INDEX라는 이름의 클러스터드 인덱스를 만든다.
그리고 이 동작은 MySQL 공식 문서에도 정리되어 있다.
MySQL docs를 살펴보면 아래와 같이 나와있다.
참고: https://dev.mysql.com/doc/refman/8.0/en/innodb-index-types.html
처음엔 이런 생각이 들었다.
PK, UK가 존재하는 경우에는 해당 컬럼을 통해 행에 빠르게 접근할 수 있으니 속도 측면에서 이점이 발생한다.
그런데 PK도 없고 UK도 없는 테이블은 보통 "특정 row를 찍어서 조회"하기보다는, 조건 없이 훑거나(풀스캔) 집계하는 용도로 쓰일 가능성이 높다.
그렇다면 굳이 클러스터드 인덱스를 만들어서 정렬까지 유지하는 게 무슨 의미가 있을까? 라는 의문이 들었다.
근데 지금 와서 보면 이 고민은… 클러스터드 인덱스의 장점을 제대로 고려하지 않은 고민이었다. 😅
클러스터드 인덱스는 데이터가 물리적으로(정확히는 InnoDB 페이지 구조 상) 정렬된 상태로 저장되는 구조이고,
따라서 전체 탐색을 주로 수행하는 테이블이라면 데이터에 순차적으로(sequential) 액세스할 확률이 높아져 I/O 효율이 좋아질 수 있다.
정리하면, PK/UK가 없어서 "조건 탐색"이 애매한 테이블이라도,
클러스터드 구조 자체가 스캔 비용에 영향을 줄 수 있기 때문에 InnoDB는 결국 클러스터드 인덱스 기반 저장을 기본값으로 유지하는 쪽을 택한 것으로 이해했다.
위에서 말한 것처럼, PK 없는 테이블은 특정 row를 찍기보단 스캔/집계 형태로 쓰일 가능성이 있다.
이 경우 클러스터드 구조는 데이터 접근 패턴을 더 "순차적"으로 만들어 줄 수 있고, 결과적으로 스캔 비용에 영향을 줄 수 있다.
PK 없는 테이블을 쓰는 상황 자체가 흔하진 않지만, 예를 들면 이런 경우가 있을 수 있다.
이런 경우는 특정 키로 딱 찍기보다,
같은 패턴이 더 많을 수 있다.
클러스터드 인덱스는 레코드가 정렬되어 저장되므로
디스크 접근이 순차적(sequential) 으로 일어나기 쉬워지고,
이는 랜덤 I/O가 많은 테이블 스캔보다 유리해질 수 있다.
여기서 자주 언급되는 비교가 이거다.
- table scan vs clustered index scan
https://stackoverflow.com/questions/18764/whats-the-difference-between-a-table-scan-and-a-clustered-index-scan
여기까지 읽으면 "PK 없어도 어차피 InnoDB가 알아서 만들어주네?" 싶을 수 있는데,
실무적으로는 PK를 두는 게 거의 항상 낫다.
왜냐면 "숨겨진 클러스터링 키"가 생기는 순간, 비용이 생긴다.
InnoDB의 세컨더리 인덱스는 리프에 "PK 값"을 같이 저장한다.
그래서 인덱스로 찾아간 뒤 PK로 다시 클러스터드 인덱스를 타고 실제 레코드를 찾는다.
즉 PK가 없다고 "PK 저장 비용이 사라지는 게 아니라",
다른 형태로 비용이 생길 수 있다.
PK가 없으면
"DB는 된다"와 "운영 가능한 설계"는 다른 문제라서,
실제로는 PK 없는 테이블이 점점 부담으로 돌아올 가능성이 크다.
InnoDB는 기본적으로 클러스터드 인덱스 기반 저장 구조를 가진다.
그래서 PK가 없더라도,
GEN_CLUST_INDEX를 만들어 사용한다.따라서 "PK가 없으면 클러스터드 인덱스가 없다"는 오해는 틀렸고,
오히려 "PK가 없어도 클러스터드 인덱스는 반드시 있다"가 핵심이다.
다만 PK가 없으면 세컨더리 인덱스 비용/운영 난이도 등으로
실무적으로는 PK를 두는 게 거의 항상 유리하다.
결론: InnoDB는 PK가 없더라도 모든 테이블을 클러스터드 구조로 유지하기 위해
내부적으로라도 클러스터링 키를 만들고, 그 결과 클러스터드 인덱스는 항상 존재한다…! 😅