본질식별자 vs 인조식별자
- 엔티티는 반드시 데이터를 식별할 수 있는 속성이 존재해야 하며 이를 식별자라고 지칭
- 식별자는 대체 여부에 따라 본질식별자와 인조식별자로 분류 가능
- 본질식별자: 업무에 의해 만들어진 식별자
- 인조식별자: 업무적으로 만들어지지는 않지만 본질식별자가 복잡한 구성을 갖고 있으므로 인위적으로 만든 식별자
가. 본질 식별자
- [그림 I-2-21] 주문상품 모델의 식별자가 본질식별자
- 주문상품 모델은 주문 시 구매한 상품 정보를 관리
- 아래 표는 하나의 주문에 세 개의 상품을 구매한 것을 데이터로 표현했으며 이러한 데이터로 개발을 진행하여 주문상품 모델에 값을 Insert 하는 경우 다음과 같이 SQL로 표현 가능
주문번호 | 상품번호 | 주문수량 |
110001 | 1234 | 1 |
110001 | 1566 | 5 |
110001 | 234 | 2 |
나. 인조 식별자의 나쁜 예
- [그림 I-2-22] 모델은 주문상품번호라는 새로운 식별자를 생성하였고 해당 식별자를 외부식별자라고 지칭
- 해당 모델의 Insert문은 다음과 같음
- 해당 SQL문은 `주문상품번호SEQ`라는 시퀀스 객체를 생성하고 NEXTVAL 기능을 이용하여 자동으로 값을 채번하여 Insert 하는 방식
- 오히려 불필요한 시퀀스를 생성하기 때문에 앞서 본질식별자에 비해 이점이 없어 보임
- 별다른 이점이 없어 보임에도 위와 같이 모델을 생성하는 가장 큰 이유는 본질식별자에 대해 고민하지 않았기 때문
- 대체로 모델에 대한 이해도가 높지 않은 상태에서 모델을 설계하다 보면, 식별자는 유일성과 존재성 (Unique, Not Null)만 만족하면 된다고 생각할 수 있기 때문
- DBMS에서 기본 키를 생성하면 Unique와 Not Null 제약이 생기므로 데이터 입력 시 오류가 발생함
- 즉 데이터 입력 시 에러가 발생하는 것에 대해서만 고려하고, 실제 해당 엔티티의 본질식별자에 대한 고민을 하지 않았기 때문에 [그림 I-2-22]와 같은 모델이 만들어짐
다. 인조식별자를 사용했지만 애매한 예
- 하나의 주문에 동일 상품을 중복 구매하고 싶을 때 [그림 I-2-21] 모델에서는 상품번호가 중복되기 때문에 불가능할 것
- [그림 I-2-23]의 주문상세 모델은 상품번호를 식별자로 구성하지 않고 하나의 주문에 발생하는 상품의 Count를 주문순번이라는 속성으로 식별자를 구성
주문번호 | 주문순번 | 상품번호 | 상품명 | 배송지 |
110001 | 1 | 1234 | 제주감귤 1box | 우리집 |
110001 | 2 | 1234 | 제주감귤 1box | 부모님집 |
110001 | 3 | 1234 | 제주감귤 1box | 친구집 |
- 위 표를 보면, 동일 상품을 하나의 주문에서 처리하고 있음
- 즉 쇼핑몰에서 동일한 상품 몇 개를 각기 다른 배송지에 보내고 싶은 요건을 나타낸 것
- 만약 [그림 I-2-21]의 주문상품 모델이라면 위와 같은 요건을 처리하기 위해서 주문을 별도로 세 번 해야 할 것
- 반면, [그림 I-2-23] 모델로 개발할 경우 다음과 같이 SQL이 호출될 것
- 이전 모델과 다른 점은 주문순번 값을 위해 하나의 주문에 구매하는 상품의 Count를 계산하여 입력한다는 것
- 이와 같은 작업은 어려운 일은 아니더라도 번거로운 작업이 추가된 것은 분명한 사실
라. 인조식별자를 적절히 사용한 예
- [그림 I-2-24] 주문상세 모델은 식별자를 주문상세번호로 정의
- 이전 모델과 차이점은 식별자를 하나의 속성으로 구성한 외부식별자로 생성
- 주문순번 속성이 사라졌지만 대신 주문상세번호가 생성되었으므로 언뜻 보면 큰 차이 없어 보이지만 실제 개발 시 편의성이 향상되는 방식
주문상세번호 | 주문번호 | 상품번호 | 상품명 | 배송지 |
1 | 110001 | 1234 | 제주감귤 1box | 우리집 |
2 | 110001 | 1234 | 제주감귤 1box | 부모님집 |
3 | 110001 | 1234 | 제주감귤 1box | 친구집 |
- 앞서 `다 사례`의 표와 비교했을 때 주문순번이 주문상세번호로 바뀐 것 말고는 다른 점이 없어 보이지만 실제 해당 값을 구하는 방식을 비교해 보면 차이점을 알 수 있음
- 주문순번은 하나의 주문번호에 대해 구매가 일어나는 상품의 Count를 구하는 것이므로 시퀀스 객체를 활용할 수 없어 별도로 작업을 해줘야 함
- 반면, 주문상세번호는 단일식별자로 구성된 키 값이기 때문에 시퀀스 객체로 해결이 가능하므로 별도 작업이 필요 없음
- 아래 SQL을 보면 더 명확하게 이해 가능
부연 설명
- `주문상세번호SEQ`라는 시퀀스 객체를 만들고 NEXTVAL을 활용하면 기본 키에 대한 부분은 더 이상 신경 쓰지 않아도 되므로 실제 작업량이 줄어듦
- 이처럼 인조 식별자를 적절히 사용하면 편하게 개발할 수 있지만 다음과 같은 문제점이 있음
- 중복 데이터로 인한 품질
- 불필요한 인덱스 생성
1. 중복 데이터로 인한 품질 문제
- 외부식별자를 사용하면 중복 데이터를 막을 수 없음
- 기본 키의 제약을 활용한다면 중복 데이터를 원천 차단할 수 있지만
- 외부식별자는 기본 키를 인위적으로 생성한 속성으로 정의하였기 때문
- 위 SQL의 두 번째 INSERT 문이 로직 오류로 인해 중복으로 발생되었다고 가정했을 때 중복된 데이터를 막을 수 없음
- 기본 키를 인위적인 인조식별자로 구성하였으므로 기본 키 제약은 주문상세번호에 대해 적용되어 있기 때문
- 위와 같은 상황이 발생헀을 때 실제 데이터는 아래와 같이 저장될 것
주문상세번호 | 주문번호 | 상품번호 | 상품명 | 배송지 |
1 | 110001 | 1234 | 제주감귤 1box | 우리집 |
2 | 110001 | 1234 | 제주감귤 1box | 우리집 |
3 | 110001 | 1234 | 제주감귤 1box | 부모님집 |
4 | 110001 | 1234 | 제주감귤 1box | 친구집 |
부연 설명
- 주문 상세번호에 기본 키 제약이 적용되어 있고, 주문상세번호는 시퀀스를 사용하였기에 제약에 위배된 사항이 없기 때문에 두 번째 행 데이터가 중복으로 발생된 데이터임에도 저장된 것을 확인 가능
- 만약 인조식별자가 아니라 `주문번호+주문순번`와 같이 본질식별자로 구성했다면 기본 키 제약조건에 의해 중복 저장을 방지할 수 있었을 것
- 그러므로 최대한 본질식별자를 지향해야 하며
- 만약 외부식별자를 사용했다면, DBMS에서는 해당 경우를 막아줄 수 없기에 애플리케이션에서 이를 방지해줘야 함
2. 불필요한 인덱스 생성
본질식별자와 인조식별자를 사용했을 때 인덱스 구성에 대해 다음과 같은 차이점이 발생합니다.
- 주문상품 모델 데이터에 접근한다고 가정했을 때 가장 기본적인 액세스 패턴은 다음 SQL과 같을 것
- 가장 기본적이면서 일반적인 액세스 패턴
- 이러한 SQL에 대해 본질식별자로 구성하면 PK 인덱스를 활용할 수 있겠지만, 인조식별자로 구성한다면 [그림 I-2-25]의 IX1과 같은 인덱스를 추가로 생성해주어야 할 것
- 즉, 인조식별자를 사용한다면 불필요한 인덱스를 추가로 생성해야 하는 점을 명심해야 함
- 또한 추가로 생성한 인덱스는 요량과 DML 성능에 영향을 줄 수 있음을 염두에 둬야 함
정리
- 식별자의 속성이 너무 많아지는 경우 본질식별자와 인조식별자의 장단점을 따져보고 사용하자
- 인조식별자의 남용을 피하고 꼭 필요한 경우에만 사용하는 것이 바람직함
참고
SQL 전문가 가이드 2020 개정판 - 한국데이터산업진흥원
반응형
'DB > SQL 전문가 가이드' 카테고리의 다른 글
[과목 II 1장 2절] SELECT 문 (0) | 2025.03.22 |
---|---|
[과목 II 1장 1절] 관계형 데이터베이스 개요 (0) | 2025.03.22 |
[과목 I 2장 4절] Null 속성의 이해 (0) | 2025.03.17 |
[과목 I 2장 3절] 모델이 표현하는 트랜잭션의 이해 (0) | 2025.03.17 |
[과목 I 2장 2절] 관계와 조인의 이해 (1) | 2025.03.17 |