본문 바로가기
데이터베이스(DB)/SQLD

[SQLD 1-2-5] 데이터 모델과 SQL - 본질식별자 vs 인조식별자

by 송기동 2024. 5. 12.
728x90

# 식별자 개념

 

[SQLD 1-1-5] 데이터 모델링의 이해 - 식별자

1. 식별자 개념- 하나의 엔터티에 구성되어 있는 여러 개의 속성 중에 엔터티를 대표할 수 있는 속성- 엔터티 내 유일한 인스턴스를 식별할 수 있는 속성의 집합- 하나의 엔터티는 반드시 하나의

thdrlehd.tistory.com


주문상품(주문상세) 모델의 식별자가 본질식별자이다. 주문상품(주문상세) 모델은 주문 시 구매한 상품 정보를 관리한다.

하나의 주문에 3개의 상품을 구매하는 경우를 SQL문으로 나타내면 다음과 같을것이다.

크게 어려울 것 없이 해당 주문에 구매하는 상품에 대한 정보를 INSERT하면 된다. 

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

 

 

하지만 다음과 같은 모델이 있다고 생각해보자.

 

위의 모델은 주문상품번호라는 새로운 식별자를 생성하였다.

이 식별자를 외부식별자라고 배웠다.

 

이와 같은 모델의 INSERT문은 다음과 같을것이다.

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

 

'주문상품번호SEQ'라는 시퀀스 객체를 생성하고 NEXTVAL 기능을 이용하여 자동으로 값을 채번하여 INSERT 하는 방식이다. 

이전 모델에 비해 좋은점이 전혀 없다. 오히려 불필요한 시퀀스를 생성 할 뿐이다. 이런모델을 생성한 이유는 무엇일까?

 

가장 큰 이유는 본질식별자에 대해 고민하지 않았기 때문이다. 대체로 모델에 대한 이해도가 높지 않은 상태에서 모델을 설계하다보면, 식별자는 유일성과 존재성(Unique, Not null)만 만족하면 된다고 생각할 수 있기 때문이다.

 

기본키(Primary Key)를 생성하면 Unique와 Not null 제약이 생기므로 데이터 입력 시 오류가 발생한다.

즉, 데이터 입력 시 에러가 발생하는 것에 대해서만 고려하고, 실제 해당 엔터티의 본질 식별자에 대한 고민을 하지 않았기 때문이다.

 

이번에는 조금 다른 경우를 살펴보자.

 

하나의 주문에 동일상품을 중복으로 구매하고 싶다면, 상품번호가 중복되기때문에 첫번째 모델에서는 불가능하다.

위의 주문상세모델은 상품번호를 식별자로 구성하지 않고 하나의 주문에 발생하는 상품의 count 를 주문순번이라는 속성으로 식별자를 구성하였다.  다음은 위 모델의 주문상세 테이블이다.

 

 

주문상세테이블에서 보면, 동일상품을 하나의 주문에서 처리하고있다. 입력은 다음과 같을것이다.

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

 

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

어려운 일은 아니더라도 번거로운 작업이 추가되었다.

 

그래서 다음과 같은 모델을 발견할 수 있다.

 

이번 모델은 식별자를 주문상세번호로 정의하였다.

이전 모델과 차이점은 식별자를 하나의 속성으로 구성한 외부식별자로 생성하였다.

주문순번 속성이 사라졌지만 대신 주문상세번호가 생성되어 개발의 편의성이 향상되었다.

어떻게 향상되었는지 주문상세 테이블과 SQL문으로 알아보자.

 

위의 본질식별자 주문상세와 비교하면 주문순번이 주문상세번호로 바뀐 것 말고는 다른점이 없어보인다.

하지만 해당 값을 구하는 방식을 비교해보면 차이점을 알 수 있다.

주문순번은 하나의 주문번호에 대해 구매가 일어나는 상품의 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', '친구집');

 

'주문상세번호SEQ'라는 시퀀스 객체를 만들고 NEXTVAL을 활용하면 기본키에 대한 부분은 더이상 신경쓰지 않아도 된다.

실제 작업량이 줄어들기 때문에 이러한 방식을 선호할수도 있지만, 위 방식에도 문제점이 있다.

 

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

- 중복 데이터로 인한 품질

- 불필요한 인덱스 생성

 

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

 

위SQL의 두 번째 Insert문이 로직 오류로 인해 중복으로 발생되었다고 하자.

이럴 경우 중복된 데이터를 막을 수 있을까? 결론은 막을수 없다.

기본키를 인위적인 인조식별자로 구성하였으므로 기본키 제약은 주문상세번호에 대해 적용되어 있기 때문이다.

그로인해 실제 데이터는 다음과 같이 저장되었을것이다.

위와 같이 중복으로 발생된 데이터임에도 저장이 된것을 볼 수 있다.
왜냐하면 주문상세번호에 기본키 제약이 적용되어 있고, 주문상세번호는 시퀀스를 사용하였기에 제약에 위배한 사항이 없기 때문

정리해보자면 최대한 본질식별자를 지향해야 한다.

만일 외부식별자를 사용했다면 DBMS에서는 막아줄 수 없기때문에 어플리케이션에서 방어 해주어야 한다.

 

2. 불필요한 인덱스 생성

본질식별자와 인조식별자를 사용했을 때 인덱스 구성은 다음과 같다.

 

위의 주문상품 모델 데이터에 액세스 한다고 하면, 가장 기본적인 엑세스 패턴은 다음 SQL과 같을 것이다.

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

 

이러한 SQL에 대해 본질식별자로 구성하면 PK인덱스를 활용할 수 있겠지만, 인조식별자로 구성한다면 IX1과 같은 인덱스를 추가로 생성해주어야 할 것이다.

즉, 인조식별자를 사용하면 불필요한 인덱스를 추가로 생성해야 한다.

또한 추가로 생성한 인덱스는 용량과 DML 성능에 영향을 줄 수 있음에 염두에 둬야 한다.

인조식별자를 무조건 사용하지 말라는 것은 아니다.

식별자의 속성이 너무 많아지는 경우 본질식별자와 인조식별자의 장단점을 따져보고 사용하자는 것이다.

인조식별자의 남용을 피하고 꼭 필요한 경우에만 사용하는 것이 바람직하다.

 

 

출저 : SQL 전문가 가이드
728x90