왜 Primary Key가 1, 2, 3이예요?

insukL·2024년 3월 13일
4
post-thumbnail

서론

데이터베이스에 대해 이론을 익히고, 설계를 진행하다보면 이론이랑 다른 점이 꽤 보인다. 그 때, 가장 이해하지 못했던 부분이 기본키가 1, 2, 3과 같은 단순 숫자인 경우였다. 매번 예시로 대학생 학번 같은 예시를 들면서 이게 무슨 경우인지 정말 헷갈렸다.

그 때 기본키에 대해 가진 의문을 정리해본 글이다. 그런 의미에서 FK를 사용하지 않는 DB와 시리즈로 봐도 좋을 것 같다.

Primary Key

당연히 설명을 위해 Primary Key의 개념이 필요하다.

키? Key?

데이터베이스는 릴레이션 안에서 하나의 레코드(row)에 대해 정합성을 보장한다. 그래서 레코드를 하나의 고유한 값으로 보장하는 것을 지원하는데, 여기에 사용되는 것이 키(Key)다.

데이터베이스는 키를 바탕으로 레코드가 고유한 값임을 보장하고, 레코드를 찾거나 정렬을 수행하는 데 사용한다.

키의 종류

키는 알겠는데, 기본키(Primary Key)는 무엇일까. 어떤 값을 보고 기본키라고 하는 걸까. 키의 종류를 알아보자.

슈퍼키

각 레코드를 유일하게 식별할 수 있는 유일성을 갖춘 키다. 유일성이란 표현을 사용했지만 앞서 말한 키의 개념을 그대로 옮겨 쓴 것이다.

후보키

슈퍼키 중에서 최소성을 만족하는 키를 말한다. 키는 하나의 값만 사용하지 않는다. 극단적으로 말하면 레코드 하나가 하나의 키가 될 수 있다. 하지만 그렇게 키에 종속된 값이 많으면 조금만 값이 수정되어도 다른 레코드로 인식될 것이다. 그래서 사용할 수 있는 값이 가장 작은 집합으로 키를 만드는데, 이것이 최소성이다.

예를 들어 어제 내가 카레를 먹었다고 하자. 오늘은 라면을 먹었다. 그러면 카레를 먹은 나라면을 먹은 나는 다른 사람일까? 라는 값이 고유함을 증명하는데, 메뉴가 키에 포함되면서 문제가 생겼다. 우리는 적절하게 레코드의 고유성을 확인하기 위해 최소성을 만족해야 한다.

기본키

유일성최소성을 만족한 키 중에서 하나를 선택하여 사용하는 키다. 레코드를 식별할 때, 기준이 되는 키로 개체 무결성에 의해 레코드는 반드시 하나의 기본키를 가진다.

MySQL과 같은 InnoDB는 기본키를 클러스터드 인덱스(Clustered Index)로 사용하기 때문에 신중히 선택해야 한다.

인덱스에 대해선 해당 포스팅에선 별도로 다루지 않고 다른 포스팅을 통해 다룰 생각에 있다.

대체키

기본키를 선정하고 남은 후보키를 대체키라고 한다.

자연키와 인조키

앞서 설명한 기본키를 설정함에 대해 우리는 자연키를 사용하는 방법과 인조키를 생성해서 사용하는 방법이 있다.

자연키

흔히 데이터베이스를 배우면서 설정하는 교과서적인 키다. 데이터 측면에서 볼 때, 고유한 값임을 확인할 수 있는 컬럼을 말한다. 대표적인 예시로 학번, 이메일, 아이디, 품번 같이 한 객체가 가지고 사용하는 데이터를 들 수 있다.

자연키를 사용하는 장점은 가독성이다. 학번이나 아이디가 고유한 값임을 우리는 비즈니스 로직에서 쉽게 짐작할 수 있다. 뿐만 아니라 값 자체에 의미를 가지기 때문에 별도로 해석이 필요하지 않다.

자연키의 문제점은 자연키는 변할 수 있다는 점이다. 환경은 언제나 변화하기 때문에 지금 보기엔 절대 변화가 없더라도 1년, 2년 지나면서 자연키에 대한 환경이 변할 수 있다. 기본키가 바뀐다는 것은 결국 데이터베이스 전체를 고치는 것에 가까울 것이다.

예를 들어, 주민등록번호는 우리나라에서 1명만 가질 수 있는 절대적인 기본키로 보인다. 실제로 기본키로 많이 사용됐다. 하지만 2014년 8월 개인정보보호가 강화되고 근거 없는 주민등록번호 수집이 금지되면서 더 이상 주민등록번호를 새로 받거나 저장할 수 없으므로 기본키로 유지할 수 없는 환경이 됐다.

인조키

자연키와 대비되게 오로지 키의 역할을 수행하기 위해 생성한 컬럼, 데이터를 인조키라고 하며, 이렇게 키의 역할을 기존의 키가 아닌 다른 컬럼을 만들어서 할당하기 때문에 대리키, 대체키(Surrogate Key) 라고도 한다.

인조키를 생성하면 이는 오로지 데이터베이스 내에서 키로만 사용되기 때문에 변경 가능성을 차단할 수 있다. 뿐만 아니라 비즈니스 로직과 무관한 값을 사용하므로 외부에 키를 노출할 경우에도 내부적인 정보를 노출하지 않고 키를 사용하게 할 수 있다.

그리고 인조키를 통해 2개, 3개의 컬럼을 사용하는 기본키를 인조키 1개로 줄일 수 있다. 이는 FK를 생각해보면 어떤 이점을 가지는지 알 수 있다. 다른 테이블에서 FK로 여러 기본키를 가져갈 필요 없이 하나의 키만 가져가서 해결할 수 있다. 이는 데이터베이스 구조와 쿼리문의 단순화의 이점으로 이어진다.

하지만 별도의 컬럼을 생성해서 키를 생성하므로, 추가적인 저장 공간이 소요된다는 단점이 있다.

auto increment와 UUID

인조키를 생성하는 방법에도 약간의 차이가 있는데, 두 방법에 대해 알아보자.

auto increment

MySQL의 auto_increment나 PostgreSQL의 Sequence처럼 시작해서 레코드가 추가됨에 따라 순차적으로 값을 증가시키는 방식이다.

순차적으로 값이 증가되기 때문에 자료형 내에서 유일한 값임을 보장한다. 삽입 순서에 가깝기 때문에 비교적 가독성이 좋고, 속도가 빠르다는 장점이 있다.

하지만 분산 DB 환경(샤딩)이라면 각각의 DBMS에서 생성한다면 id 값의 중복이 발생할 수 있기 때문에 어플리케이션으로 PK 생성을 옮겨 실행해야 한다.

뿐만 아니라 보안적인 문제도 있는데, 순차적으로 값이 증가함을 보장하기 때문에 오히려 외부에서 키를 예측하기 쉬워진다. 예를 들어 이번 포스팅이 1450이라는 PK를 가지면, 우리는 다음 포스팅의 PK가 1451임을 알 수 있다. 이를 통해 악의적인 공격에 노출되기 쉽고, 매크로나 크롤러와 같은 자동화 도구에 노출되기 쉽다는 문제가 있다.

UUID

UUID는 랜덤한 값을 통해 실질적으로 유효함을 추구한다. 보통 128비트 길이를 사용하는데, 생일 문제를 생각하면 랜덤한 값이지만 유효할 확률이 높음을 예상할 수 있다.

개발 환경에 대해 온전히 독립적으로 랜덤하게 생성된 값이기 때문에, 생성만 할 수 있으면 된다. 다시 말하면 분산 DB 환경에서도 생성만 하면 키로 사용할 수 있어 추가적인 작업을 해주지 않아도 된다. 뿐만 아니라 순차적으로 값이 증가하지 않으므로 순차적으로 접근한들 모든 데이터를 찾을 수 없다.

하지만 UUID는 길고 랜덤한 값을 사용하기 때문에 가독성이 떨어지고, 많은 용량을 차지한다. 그리고 전체가 랜덤한 값이므로 정렬이 필요한 데이터에 대해 별도의 컬럼을 사용해야 한다는 단점이 있다.

그래서 어떻게 하라구요?

방법을 보면 전부 일장일단이 있다. auto increment 방식을 통해 외부에서 예측할 수 있는 정보(ex) 페이지 수, 데이터 갯수)가 많아 UUID를 쓰는게 좋아보이지만, B Tree를 쓰는 데이터베이스의 인덱스 구조상 auto increment 방식으로 인접한 데이터를 같이 저장하는 것이 더 효율적으로 보인다.

어쩌면 합치는 방법도 고려해볼 수 있다. 외부에 UUID를 노출하고, 내부적으로 id와 매핑하는 방식은 어떨까? auto increment를 주 인덱스로, UUID를 보조 인덱스로 사용해서 저장 공간을 많이 사용하지만 외부와 내부에서 사용할 검색값을 모두 저장하는 방식도 있다.

결론

매번 포스팅마다 은총알은 없다는 이야기를 한다. 하지만 이번 만큼은 자연키와 인조키의 비교에서 인조키의 손을 들어주고 싶다. 가독성을 조금 포기하더라도 변경 가능성을 차단하는 점에서 인조키를 포기하기 힘들다. 무엇보다 테이블 간의 관계를 설정할 때, 인조키를 써보면 얼마나 FK를 저장하기 쉬워지는지 느낄 수 있다.

생성 방식의 경우엔 개발 목적에 따라 충분히 다르게 선택해야 한다. 하지만 내부적으로 auto increment를 사용해도 unsigned int를 사용하면 대략 40억 데이터를 테이블에 저장할 수 있어, 충분한 저장 공간을 보장할 수 있다고 생각한다. 그래서 메인 키로 auto increment를 사용하고, 노출을 막고자 하는 데이터에 대해 보조키로 UUID를 사용하는 방법에 손을 들어주고 싶다.

참고

대체키를 PK로 설정해야 하는 이유
UUID vs Auto increment
자연키 vs 인조키

profile
데이터를 소중히 여기는 개발자가 되고 싶습니다

0개의 댓글