← 기술 지식

기술 지식 / 데이터 시스템 / 데이터베이스 / 관계 모델

정규화의 사실 분리와 모든 후보키 검사

수강·주문 사례의 수정·삽입·삭제 이상에서 출발해 1NF·2NF·3NF·다대다 관계 속성·역사 snapshot·삭제 보존을 설명합니다.

데이터베이스 · 원리·구현·운영
기술 지식 트리

정규화의 출발점은 테이블을 무조건 잘게 나누는 일이 아니라 함수 종속 X→Y가 어떤 사실의 수명과 소유자를 나타내는지 밝히는 일입니다. 같은 행에 함께 저장할 이유가 없는 사실을 분리하되, 당시 가격처럼 반복되어도 의미가 다른 snapshot은 현재 사실과 구분해 보존해야 합니다.

수강 신청·강좌의 행 결합과 사실별 수명

수강 신청 행마다 학생 이름·강좌명·교수명을 복사하면 강좌명 하나를 바꿀 때 그 강좌를 신청한 모든 행을 찾아 고쳐야 하고, 한 행을 빠뜨리면 서로 다른 이름이 남습니다. 이렇게 한 사실을 수정할 때 여러 행을 함께 고쳐야 하는 문제가 수정 이상입니다. 수강생이 없으면 강좌만 등록할 수 없거나 마지막 신청을 삭제했을 때 강좌 정보까지 사라지는 것도 각각 삽입 이상과 삭제 이상입니다. 따라서 문제는 같은 값의 반복 자체가 아니라 서로 다른 사실과 수명이 한 행에 묶인 데 있으며, 여러 신청에 같은 강좌 ID를 두는 관계 표현까지 없애면 연결 정보가 사라집니다.

학생·강좌·신청 사실의 분리

테이블 결정 관계의 예 남길 사실
Student student_id → name 학생 자체 정보
Offering offering_id → course·term·teacher 학기별 개설 정보
Enrollment (student_id, offering_id) → enrolled_at·grade 관계 자체 정보

같은 학생이 같은 개설 강좌에 한 번만 신청한다는 전제라면 복합 UNIQUE로 중복을 막고 양쪽 외래키로 존재를 강제할 수 있습니다. 재수강·취소 후 재신청 이력을 여러 행으로 남긴다면 attempt 또는 별도 관계 ID와 현재 활성 제약이 필요합니다. 관계의 성적을 학생 테이블에 옮기면 어떤 강좌의 성적인지 잃습니다.

사실의 소유자를 분리하고 관계를 명시합니다Enrollment: 학생·개설 강좌 키, 신청일·성적. Student: 학생 ID·이름. Offering: 개설 ID·학기·강좌student_idoffering_idEnrollment학생·개설 강좌 키신청일·성적Student학생 ID·이름Offering개설 ID·학기·강좌
사실의 소유자를 분리하고 관계를 명시합니다 화살표는 외래키 참조입니다. 수강 신청을 지워도 학생·개설 강좌의 독립 사실은 남고 삭제 보존 정책은 별도로 정합니다.
그림을 글로 읽기
  • Enrollment: 학생·개설 강좌 키 · 신청일·성적
  • Student: 학생 ID·이름
  • Offering: 개설 ID·학기·강좌
  1. Enrollment → Student: student_id
  2. Enrollment → Offering: offering_id

주문 예제의 부분 종속과 이행 종속

R(주문번호, 상품번호, 상품명, 수량, 고객번호, 고객명)에서 한 주문의 동일 상품은 한 행이고 상품명·고객명은 현재 정보라고 하겠습니다. (주문번호, 상품번호)가 그 상세 행을 식별하는 후보키이며 상품번호→상품명, 주문번호→고객번호, 고객번호→고객명이 성립합니다.

1NF는 한 칸에 선택한 도메인의 하나의 원자값을 두는 상태이므로 상품 목록을 문자열이나 JSON 하나에 숨긴다고 관계 제약이 자동으로 생기지 않습니다. 2NF에서는 어떤 후보키의 진부분에만 종속되는 비주요 속성이 없어야 합니다. 비주요 속성은 어느 후보키에도 속하지 않는 속성입니다. 이 예에서 상품명은 상품 테이블로, 고객번호는 주문 테이블로 분리하고 상세에는 두 ID와 수량을 남깁니다. 주문번호→고객번호→고객명처럼 중간 속성을 거쳐 고객명이 결정되는 이행 종속도 남으므로 고객명을 고객 테이블로 옮기며, 다른 저장 모델을 택하면 그 모델의 질의·검증 규칙을 별도로 설계합니다.

3NF의 superkey·Prime Attribute 조건과 모든 후보키 검사

정확한 3NF 조건은 모든 비자명 함수 종속 X→A에서 X가 superkey이거나 A가 어떤 후보키에 속하는 prime attribute인 것입니다. “비키는 키에만 의존한다”는 요약은 여러 후보키가 있을 때 예외를 놓칠 수 있습니다. 2NF 역시 선택한 기본키뿐 아니라 모든 후보키의 부분 종속을 봅니다.

예를 들어 R(A,B,C,D)에 A→BC, BC→AD, B→D가 있으면 A와 BC가 후보키일 수 있습니다. 단일 열 A를 PK로 골라도 비주요 D가 다른 후보키 BC의 일부 B에 종속되는 문제를 놓치면 안 됩니다. surrogate ID 추가가 원래 업무 종속을 지우지 않는 이유입니다. 실제 후보키·prime 속성은 폐포와 최소성으로 확인합니다.

현재 사실과 당시 Snapshot의 의미 분리

주문 당시 상품명·가격을 보존해야 한다면 현재 Product의 이름을 JOIN하는 것으로 대체하면 역사적 의미가 바뀝니다. 당시 사실은 주문 항목에 독립 snapshot으로 남을 수 있습니다. 같은 문자열이 반복된다고 항상 제거할 중복은 아닙니다.

반면 현재 이름의 조회 최적화를 위해 복사한 것은 파생 읽기 모델입니다. 원본·변경 전파·버전·재구축·불일치 탐지 책임을 정해야 합니다. 정규형을 높이기 위해 역사 의미를 바꾸거나 테이블 수를 늘리는 것 자체를 목표로 하지 않습니다.

외래키의 참조 무결성과 삭제·보관 수명 정책

외래키는 자식 행이 참조하는 부모 행의 존재 관계를 지키는 장치이지, 부모를 지울 권한이나 두 행의 보존 기간을 정하지 않습니다. CASCADE는 부모 삭제를 자식 삭제로 연쇄하고, RESTRICT·NO ACTION은 참조가 남아 있으면 삭제를 막는 정책이며, SET NULL은 nullable(NULL을 허용하는) 자식 키를 NULL로 바꿀 수 있습니다.

각 정책의 검사 시점과 deferred(제약 검사를 거래 끝으로 미루는 기능) 지원은 엔진별로 다릅니다. 주문·결제·성적 같은 이력은 계정 삭제와 같은 수명을 가져야 하는지, 익명화·개인정보 분리·접근 제한을 어떻게 할지는 업무 규칙으로 정해야 합니다.

대량 cascade는 한 부모 삭제도 많은 자식·인덱스·로그·잠금을 만들 수 있습니다. 배치로 나눌 때는 중간 상태·재개 키·동시 삽입 차단을 설계하고, ORM 옵션과 실제 DB FK가 같은지와 마지막 신청 삭제가 강좌 자체를 지우지 않는지도 시험합니다.

분해 뒤 의미 보존과 비용 검증

분해의 검산은 원본 행을 그대로 복제하는지보다 업무 사실을 재구성하는지에 초점을 둡니다. 예를 들어 주문 당시 가격은 현재 상품 가격과 달라도 주문 합계가 변하지 않아야 하고, 신청 없는 강좌는 Enrollment 행이 없어도 조회되어야 하며, 동시 신청 두 건은 UNIQUE 제약의 성공·실패가 명확해야 합니다.

이름 변경·신청 없는 강좌 생성·마지막 신청 삭제·동시 중복 신청·재수강을 합성 데이터로 검사합니다. 모든 후보키의 폐포와 부분 종속을 검산하고 조회의 JOIN 행 수·인덱스·반환 바이트를 비교합니다. 이 노트는 모델 설계이며 특정 DB에서 정규화 migration과 삭제 실험을 실행한 결과는 아닙니다.

후속·관련 개념

함께 읽을 문서

전체 기술 지식
선택 연습 · 관련 질문 5개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.