본질식별자 vs. 인조식별자

2023. 3. 7. 09:50SQL/SQL 전문가 가이드(2020 개정판)

○ 본질식별자: 업무에 의해 만들어진 식별자

○ 인조식별자: 업무적으로 만들어지지 않지만 본질식별자가 복잡한 구성을 갖고 있으므로 인위적으로 만든 식별자

주문상품 모델의 식별자가 본질식별자입니다. 주문상품 모델은 주문 시 구매한 상품 정보를 관리합니다.

 

주문상품

주문번호 상품번호 주문수량
110001 1234 1
110001 1566 5
110001 234 2

하나의 주문에 3개의 상품을 구매한 것을 데이터로 표현한 자료입니다. 이러한 데이터로 개발을 진행하여 주문상품 모델에 값을 Insert 하는 경우를 SQL로 확인해 봅시다.

INSERT INTO 주문상풍 VALUES(110001, 1234, 1);
INSERT INTO 주문상품 VALUES(110001, 1566, 5);
INSERT INTO 주문상품 VALUES(110001, 234, 2);

해당 주문에 구매한 상품에 대한 정보를 Insert 하면 됩니다.

모델은 주문상품번호라는 새로운 식별자를 생성했습니다. 이 식별자를 외부식별자라고 배웠으며, 이와 같은 모델의 Insert문을 확인해 봅시다.

INSERT INTO 주문상품 VALUES(주문상품번호SEQ.NEXTVAL, 110001, 1234, 1);
INSERT INTO 주문상품 VALUES(주문상품번호SEQ.NEXTVAL, 110001, 1566, 5);
INSERT INTO 주문상품 VALUES(주문상품번호SEQ.NEXTVAL, 110001, 234, 2);

SQL문은 '주문상품 SEQ'라는 시퀀스(Sequence) 객체를 생성하고, NEXTVAL 기능을 이용하여 자동으로 값을 채번 하여 Insert 하는 방식입니다. 대체로 모델에 대한 이해도가 높지 않은 상태에서 모델을 설계하다 보면, 식별자는 유일성과 존재성(Unique, Not null)만 만족하면 된다고 생각할 수 있습니다. 이에 DBMS에서 기본키(Primary key)를 생성하면 Unique와 Not null 제약이 생겨서 데이터 입력 시 오류가 발생합니다.

주문상세 모델은 상품번호를 식별자로 구성하지 않고, 하나의 주문에 발생하는 상품의 Count를 주문순번이라는 속성으로 식별자를 구성했습니다.

 

본질식별자 주문상세

주문번호 주문순번 상품번호 상품명 배송지
110001 1 1234 제주감귤 1box 우리집
110001 2 1234 제주감귤 1box 부모님집
110001 3 1234 제주감귤 1box 친구집

데이터를 보면, 동일상품을 하나의 주문에서 처리하고 있습니다. 쇼핑몰에서 동일한 상품 몇 개를 각기 다른 배송지에 보내고 싶은 요건을 나타낸 것입니다. 실제 자주 사용하는 쇼핑몰에서 주문을 해보면 동일상품 주문이 가능한 곳도, 그렇지 않은 곳도 알 수 있습니다.

 

INSERT INTO 주문상세 VALUES(110001, 1, 1234, '제주감귤 1box', '우리집');
INSERT INTO 주문상세 VALUES(110001, 2, 1234, '제주감귤 1box', '부모님집');
INSERT INTO 주문상세 VALUES(110001, 3, 1234, '제주감귤 1box', '친구집');

이전 모델과 다른 점은 주문순번 값을 위해 하나의 주문에 구매하는 상품의 Count를 계산하여 입력해야 합니다.

주문상세 모델은 식별자를 주문상세번호로 정의했습니다. 이전 모델과 차이점은 식별자를 하나의 속성으로 구성한 외부식별자로 생성한 점입니다. 실제 개발 시 편의성이 향상되는 방식입니다.

 

인조식별자 주문상세

주문상세번호 주문번호 상품번호 상품명 배송지
1 110001 1234 제주감귤 1box 우리집
2 110001 1234 제주감귤 1box 부모님집
3 110001 1234 제주감귤 1box 친구집

주문순번은 하나의 주문번호에 대해 구매가 일어나는 상품의 Count를 구하는 것이므로 시퀀스 객체를 활용할 수 없어 따로 작업해줘야 합니다. 그러나 주문상세번호는 단일식별자로 구성된 키값이기에 시퀀스 객체로 해결이 가능합니다.

INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '우리집');
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '부모집');
INSERT INTO 주문상세 VALUES(주문상세번호SEQ.NEXTVAL, 110001, 1234, '귤 1box', '친구집');

외부식별자를 사용하는 방식에는 크게 두 가지 문제점이 있습니다.

 

○ 중복 데이터로 인한 품질

○ 불필요한 인덱스 생성

 

1. 중복 데이터로 인한 품질 문제

외부식별자를 사용하면 중복 데이터를 막을 수 없습니다. 기본키의 제약을 활용한다면 중복 데이터를 원천 차단할 수 있지만, 기본키를 인위적으로 생성한 속성으로 정의했기 때문입니다.

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', '친구집');

 기본키를 인위적인 인조식별자로 구성하였기에 기본키 제약은 주문상세번호에 대해 적용되었습니다.

 

인조식별자 주문상세

주문상세번호 주문번호 상품번호 상품명 배송지
1 110001 1234 제주감귤 1box 우리집
2 110001 1234 제주감귤 1box 우리집
3 110001 1234 제주감귤 1box 부모님집
4 110001 1234 제주감귤 1box 친구집

주문상세번호에 기본키 제약이 적용되어 있고, 주문상세번호는 시퀀스를 사용하였기에 제약에 위배된 사항이 없습니다. 주문상세 모델의 본질 식별자의 경우를 SQL문으로 확인해 봅시다.

INSERT INTO 주문상세 VALUES(110001, 1, 1234, '제주감귤 1box', '우리집');
INSERT INTO 주문상세 VALUES(110001, 1, 1234, '제주감귤 1box', '우리집');
INSERT INTO 주문상세 VALUES(110001, 2, 1234, '제주감귤 1box', '부모님집');
INSERT INTO 주문상세 VALUES(110001, 3, 1234, '제주감귤 1box', '친구집');

두 번째 Insert 문을 보면 로직의 오류로 인해 동일한 Insert 문이 발생했지만, '주문번호+주문순번'이 식별자이기에 기본키 제약조건에 의해 두 번째 Insert문은 에러가 발생될 것입니다.

 

2. 불필요한 인덱스 생성

본질 식별자와 인조 식별자를 사용했을 때 인덱스 구성에 대해 어떤 차이가 있는지 확인해 봅시다.

주문상품 모델 데이터에 액세스한다고 가정하면 여러 가지 엑세스 패턴이 있겠지만, 가장 기본적인 엑세스 패턴은 다음 SQL과 같을 겁니다.

SELECT *
  FROM 주문상품
 WHERE 주문번호 = :B1;

또는 다음 SQL과 같을 것입니다.

SELECT *
  FROM 주문상품
 WHERE 주문번호 = :B1
 	AND 상품번호 = :B2;

가장 기본적이면서 일반적인 엑세스 패턴이며, 이러한 SQL에 대한 본질식별자로 구성하면 PK 인덱스를 활용할 수 있지만, IX1과 같은 인덱스를 추가로 생성해줘야 할 것입니다. 인조식별자를 사용한다면 불필요한 인덱스를 추가로 생성해야 하는 점을 기억해야 합니다.