본질식별자 vs 인조식별자
- 엔티티는 반드시 데이터를 식별할 수 있는 속성이 존재해야 하며 이를 식별자라고 지칭
- 식별자는 대체 여부에 따라 본질식별자와 인조식별자로 분류 가능
- 본질식별자: 업무에 의해 만들어진 식별자
- 인조식별자: 업무적으로 만들어지지는 않지만 본질식별자가 복잡한 구성을 갖고 있으므로 인위적으로 만든 식별자
가. 본질 식별자

- [그림 I-2-21] 주문상품 모델의 식별자가 본질식별자
- 주문상품 모델은 주문 시 구매한 상품 정보를 관리
- 아래 표는 하나의 주문에 세 개의 상품을 구매한 것을 데이터로 표현했으며 이러한 데이터로 개발을 진행하여 주문상품 모델에 값을 Insert 하는 경우 다음과 같이 SQL로 표현 가능
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSERT INTO 주문상풍 VALUES(110001, 1234, 1); | |
INSERT INTO 주문상품 VALUES(110001, 1566, 5); | |
INSERT INTO 주문상품 VALUES(110001, 234, 2); |
주문번호 | 상품번호 | 주문수량 |
110001 | 1234 | 1 |
110001 | 1566 | 5 |
110001 | 234 | 2 |
나. 인조 식별자의 나쁜 예

- [그림 I-2-22] 모델은 주문상품번호라는 새로운 식별자를 생성하였고 해당 식별자를 외부식별자라고 지칭
- 해당 모델의 Insert문은 다음과 같음
- 해당 SQL문은 `주문상품번호SEQ`라는 시퀀스 객체를 생성하고 NEXTVAL 기능을 이용하여 자동으로 값을 채번하여 Insert 하는 방식
- 오히려 불필요한 시퀀스를 생성하기 때문에 앞서 본질식별자에 비해 이점이 없어 보임
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSERT INTO 주문상품 VALUES(주문상품번호SEQ.NEXTVAL, 110001, 1234, 1); | |
INSERT INTO 주문상품 VALUES(주문상품번호SEQ.NEXTVAL, 110001, 1566, 5); | |
INSERT INTO 주문상품 VALUES(주문상품번호SEQ.NEXTVAL, 110001, 234, 2); |
- 별다른 이점이 없어 보임에도 위와 같이 모델을 생성하는 가장 큰 이유는 본질식별자에 대해 고민하지 않았기 때문
- 대체로 모델에 대한 이해도가 높지 않은 상태에서 모델을 설계하다 보면, 식별자는 유일성과 존재성 (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를 계산하여 입력한다는 것
- 이와 같은 작업은 어려운 일은 아니더라도 번거로운 작업이 추가된 것은 분명한 사실
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSERT INTO 주문상세 VALUES(110001, 1, 1234, '제주감귤 1box', '우리집'); | |
INSERT INTO 주문상세 VALUES(110001, 2, 1234, '제주감귤 1box', '부모님집'); | |
INSERT INTO 주문상세 VALUES(110001, 3, 1234, '제주감귤 1box', '친구집'); |
라. 인조식별자를 적절히 사용한 예

- [그림 I-2-24] 주문상세 모델은 식별자를 주문상세번호로 정의
- 이전 모델과 차이점은 식별자를 하나의 속성으로 구성한 외부식별자로 생성
- 주문순번 속성이 사라졌지만 대신 주문상세번호가 생성되었으므로 언뜻 보면 큰 차이 없어 보이지만 실제 개발 시 편의성이 향상되는 방식
주문상세번호 | 주문번호 | 상품번호 | 상품명 | 배송지 |
1 | 110001 | 1234 | 제주감귤 1box | 우리집 |
2 | 110001 | 1234 | 제주감귤 1box | 부모님집 |
3 | 110001 | 1234 | 제주감귤 1box | 친구집 |
- 앞서 `다 사례`의 표와 비교했을 때 주문순번이 주문상세번호로 바뀐 것 말고는 다른 점이 없어 보이지만 실제 해당 값을 구하는 방식을 비교해 보면 차이점을 알 수 있음
- 주문순번은 하나의 주문번호에 대해 구매가 일어나는 상품의 Count를 구하는 것이므로 시퀀스 객체를 활용할 수 없어 별도로 작업을 해줘야 함
- 반면, 주문상세번호는 단일식별자로 구성된 키 값이기 때문에 시퀀스 객체로 해결이 가능하므로 별도 작업이 필요 없음
- 아래 SQL을 보면 더 명확하게 이해 가능
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '우리집'); | |
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '부모집'); | |
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '친구집'); |
부연 설명
- `주문상세번호SEQ`라는 시퀀스 객체를 만들고 NEXTVAL을 활용하면 기본 키에 대한 부분은 더 이상 신경 쓰지 않아도 되므로 실제 작업량이 줄어듦
- 이처럼 인조 식별자를 적절히 사용하면 편하게 개발할 수 있지만 다음과 같은 문제점이 있음
- 중복 데이터로 인한 품질
- 불필요한 인덱스 생성
1. 중복 데이터로 인한 품질 문제
- 외부식별자를 사용하면 중복 데이터를 막을 수 없음
- 기본 키의 제약을 활용한다면 중복 데이터를 원천 차단할 수 있지만
- 외부식별자는 기본 키를 인위적으로 생성한 속성으로 정의하였기 때문
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '우리집'); | |
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '우리집'); | |
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '우리집'); | |
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '부모집'); | |
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '친구집'); |
- 위 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 성능에 영향을 줄 수 있음을 염두에 둬야 함
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 예시 1 | |
SELECT | |
* | |
FROM | |
주문상품 | |
WHERE | |
주문번호 = :B1; | |
// 예시 2 | |
SELECT | |
* | |
FROM | |
주문상품 | |
WHERE | |
주문번호 = :B1 | |
AND | |
상품번호 = :B2; |
정리
- 식별자의 속성이 너무 많아지는 경우 본질식별자와 인조식별자의 장단점을 따져보고 사용하자
- 인조식별자의 남용을 피하고 꼭 필요한 경우에만 사용하는 것이 바람직함
참고
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 |