578. 수퍼타입/서브타입 데이터의 물리 테이블 변환 모델
⚠️ 이 문서는 논리적 데이터 모델링(ERD)에서 공통된 속성을 가진 '수퍼타입(예: 사원)'과 특수한 속성을 가진 '서브타입(예: 정규직, 계약직)'을 나누어 그렸을 때, 실제 오라클이나 MySQL 같은 물리적 데이터베이스를 구축할 때 이 그림을 어떻게 실제 테이블로 찢거나 합칠 것인가에 대한 3가지 전략(OneToOne, Plus, Single)과 그에 따른 쿼리 성능(Performance)의 득실을 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: 도화지(논리 모델)에 그려진 객체 지향적 '상속' 관계를 투박한 2차원 표(물리 테이블)로 찍어낼 때 쓰는 번역법이다.
- 가치: 데이터를 조회하는 쿼리의 패턴(전체 사원을 주로 조회하는가, 계약직만 따로 조회하는가)에 따라 테이블을 합치거나 쪼개는 전략을 다르게 가져가야 하며, 잘못 선택하면 매번 거대한 뷰(View)나 무거운 조인(JOIN)이 발생해 쿼리 성능이 박살 난다.
- 기술 체계: 그림 생긴 그대로 1:1로 찢어놓는 OneToOne(1:1) 타입, 서브타입에 부모의 공통 속성을 내려보내 각각 독립 테이블로 찢어발기는 Plus 타입, 모든 속성을 거대한 테이블 하나에 몽땅 다 때려 박고 널(Null)을 허용하는 Single(통합) 타입이 있다.
Ⅰ. 수퍼/서브타입의 이해와 논리적 아름다움
공통점은 모으고 차이점은 밑으로 빼라. 객체 지향의 철학이다.
- 수퍼타입(Supertype)과 서브타입(Subtype):
- 회사에 '정규직 사원'과 '계약직 사원'이 있다.
- 둘 다 사원이므로
[사번, 이름, 부서]라는 공통 속성을 가진다. 이를 묶어 최상단에 수퍼타입[사원]엔티티를 그린다. - 그런데 정규직은
[퇴직금, 정규직_호봉]이라는 특수 속성이 있고, 계약직은[계약_종료일, 시급]이라는 특수 속성이 있다. 이를 수퍼타입 아래에 화살표로 상속받는 서브타입[정규직],[계약직]엔티티로 쪼개어 그린다.
- 논리 모델의 한계 (RDBMS에는 상속이 없다):
- 이 그림은 인간이 보기엔 완벽하고 아름답다. 하지만 RDBMS 테이블을 만들 때는 난감해진다.
- RDBMS에는 '상속'이라는 기능이 없기 때문에 DBA는 이 그림을 보고 물리 테이블을 몇 개로 만들지 결단을 내려야 한다.
📢 섹션 요약 비유: 서류함을 정리할 때, '인간 서류함(수퍼타입)' 안에는 모든 직원의 이름과 전화번호가 들어있습니다. 그리고 '정규직 서류함(서브타입)'과 '계약직 서류함(서브타입)'을 따로 만들어서 거기에만 들어가는 특수 서류를 보관합니다. 인간의 머리로는 완벽한 분류법이지만, 정작 컴퓨터 엑셀(RDBMS)에 이 서류함을 입력하려니 "이걸 엑셀 시트 1장에 다 합쳐서 그릴까? 아니면 시트 3장으로 나눠서 탭을 만들까?"라는 물리적인 설계 고민에 빠지게 됩니다.
Ⅱ. 3가지 물리 변환 전략과 쿼리 성능(Performance)
테이블을 어떻게 찢을 것인가? 쿼리의 모양이 모든 것을 결정한다.
1. OneToOne 타입 (1:1 분리 모델)
그림 생긴 그대로 테이블을 3개 만든다.
- 구조:
사원(공통)테이블 1개,정규직(특수)테이블 1개,계약직(특수)테이블 1개를 각각 물리적으로 만든다. 정규직 테이블은사원번호를 부모(사원)와 똑같이 PK 겸 FK로 갖는다 (1:1 관계). - 장점: 논리 모델과 100% 똑같이 생겨서 직관적이며, 테이블의 빈칸(Null)이 거의 발생하지 않는다.
- 단점 (조인의 저주): "김대리(정규직)의 이름(수퍼타입)과 퇴직금(서브타입)을 같이 보여줘!"라고 쿼리하려면 무조건
사원과정규직두 테이블을 **조인(JOIN)**해야 한다. 데이터가 1,000만 건이면 조인하다 날이 샌다.
2. Plus 타입 (서브타입 테이블 분할 모델)
수퍼타입을 없애버리고, 부모의 유산을 자식들에게 다 물려준다.
- 구조:
사원테이블은 아예 만들지 않는다. 대신정규직테이블 안에[사번, 이름, 부서, 퇴직금, 호봉]을 다 넣고,계약직테이블 안에도[사번, 이름, 부서, 계약종료일, 시급]을 다 넣는다. 테이블이 딱 2개만 남는다. - 장점: "모든 정규직 데이터 다 가져와!"라는 쿼리가 잦을 때, 조인 없이 테이블 1개만 싹 뒤지면 되므로 엄청나게 빠르다.
- 단점 (UNION의 늪): 사장님이 "회사 전체 사원(정규+계약) 이름 다 뽑아와!"라고 하면, 정규직 테이블과 계약직 테이블을 억지로 이어 붙이는 UNION ALL 연산이 무조건 발생하여 성능이 박살 난다.
3. Single 타입 (통합 테이블 모델) $\star$
그냥 엑셀 시트 1개에 몽땅 다 때려 박는다.
- 구조:
사원_통합이라는 거대한 테이블 딱 1개만 만든다. 컬럼은[사번, 이름, 부서, 사원구분_코드, 퇴직금, 호봉, 계약종료일, 시급]이 몽땅 다 들어간다. - 장점: 테이블이 1개이므로 JOIN도 없고 UNION도 없다. 전체 직원을 찾든 특정 직원을 찾든 쿼리가 압도적으로 제일 빠르고 단순하다. (실무에서 가장 많이 쓰이는 국룰이다.)
- 단점 (Null의 바다): 김대리가 정규직이라면, 그의 데이터 줄(Row)에서 '계약종료일, 시급' 칸은 영원히 텅 빈 쓰레기 공간(Null)으로 남는다. 하드디스크 용량 낭비가 조금 발생하지만, 요즘은 스토리지 값이 껌값이라 이 정도는 무시한다.
📢 섹션 요약 비유: 서류함을 합치는 3가지 방법입니다. OneToOne은 [공통 서류철 1개]와 [특수 서류철 2개]를 따로 두어, 직원 1명 찾을 때마다 철을 2개씩 뒤져야(JOIN) 하는 피곤한 방식입니다. Plus는 [정규직 전용 서류철]과 [계약직 전용 서류철] 딱 2개만 만들어서 자기들끼리 볼 땐 빠르지만, "전 직원 명부 가져와!" 하면 알바생이 철 2개를 바닥에 쏟아놓고(UNION) 섞어야 하는 노가다입니다. Single은 그냥 [초대형 전 직원 서류철] 딱 1개만 만들어서 모든 양식을 욱여넣고, 빈칸은 쿨하게 비워두는(Null) 가장 현실적이고 빠른 만능 철입니다.
Ⅲ. 실무 결단: 어떤 잣대로 선택할 것인가?
데이터의 양(Volume)과 트랜잭션 패턴이 설계자를 심판한다.
- 트랜잭션(Transaction) 패턴에 따른 선택:
- 업무의 90%가 '전체 사원'을 뭉뚱그려 처리하는 일(일괄 급여 계산, 전체 명부 조회)이라면? 묻지도 따지지도 않고 **Single 타입(통합)**으로 가야 성능이 보장된다.
- 반대로 '정규직'과 '계약직'이 하는 업무가 90% 확률로 완전히 찢어져 있고(서로 만날 일이 없음), 각각의 테이블이 수천만 건으로 거대하다면? **Plus 타입(분할)**으로 찢어두는 게 인덱스 효율상 훨씬 유리하다.
- 속성의 개수와 Null의 한계:
- 만약 서브타입에 달린 특수 컬럼이 1~2개가 아니라, 정규직 100개, 계약직 100개, 일용직 100개 식으로 엄청나게 많다면?
- 이걸 Single 타입 1개로 다 때려 박으면 컬럼이 300개가 넘는 괴물 테이블(Row-chaining 위험)이 탄생하고, 빈칸(Null)이 90%를 차지하는 끔찍한 I/O 병목이 터진다. 이때는 어쩔 수 없이 조인(JOIN)을 감수하고서라도 OneToOne 타입으로 찢어둬야 데이터베이스가 숨을 쉴 수 있다.
📢 섹션 요약 비유: Single(통합) 엑셀 파일은 만능이지만 칸(컬럼)이 100개, 200개로 넘어가면 엑셀이 열리다 뻗어버립니다. 속성(컬럼)이 몇 개 없고 전체를 다 같이 보는 일이 많다면 과감하게 엑셀 1장(Single)으로 뭉쳐서 편하게 일하십시오. 하지만 각 부서(서브타입)가 봐야 할 고유 컬럼이 수십 개씩 너무 많고 서로의 데이터를 볼 일이 없다면, 엑셀을 탭(Plus)이나 파일(OneToOne)로 쪼개서 각자 관리하는 것이 데이터베이스(엑셀)가 죽지 않게 살려주는 설계자의 지혜입니다.