데이터 모델링·ERD — 개념·논리·물리 설계
테이블을 바로 만들기 전에 — 무엇을 저장하고, 무엇이 무엇과 어떻게 연결되는지를 그리는 게 데이터 모델링이다. 잘못된 모델은 나중에 정규화·인덱스·쿼리로도 못 고치는 근본 부채가 된다(엔터티를 잘못 나누면 모든 쿼리가 비틀린다). 모델링은 추상(개념)에서 구체(물리)로 세 단계를 거치며, 그 그림 언어가 **ERD(개체-관계 다이어그램)**다. (출처: Silberschatz DBSC 7e Ch 6 E-R Model / 김연희 데이터베이스 개론.)
세 단계 — 개념 → 논리 → 물리
세 단계를 "쇼핑몰" 요구사항으로 따라가 보자.
- 개념적(conceptual) — 무엇이 있고 어떻게 관계되나를 DBMS와 무관하게 그린다. "회원이 주문을 하고, 주문에는 여러 상품이 담긴다" → 엔터티 회원·주문·상품, 관계 주문하다·담기다. 이해관계자(기획·도메인 전문가)와 합의하는 단계라 기술 용어를 최소화한다.
- 논리적(logical) — 개념을 *관계 모델(테이블·키·정규화)*로 변환한다. 회원→member 테이블, 주문→orders, "주문에 상품 담기"(N:M)→order_item 연결 테이블. 어떤 DBMS든 적용 가능한 수준이다.
- 물리적(physical) — 특정 DBMS에 맞춰 타입·인덱스·파티션·스토리지를 정한다.
created_at을 PostgreSQL이면timestamptz, MySQL이면DATETIME으로, PK는IDENTITY/AUTO_INCREMENT로, 자주 조회하는member_id엔 인덱스를 건다.
개념은 사람이 이해하고, 논리는 모든 DB에 통하고, 물리는 한 DB에 최적화한다 — 단계를 건너뛰고 바로 테이블부터 만들면 요구사항과 어긋난 구조가 굳는다.
엔터티·속성·관계·식별자
| 모델링 개념 | 관계형(테이블) | 예 |
|---|---|---|
| 엔터티(entity) | 테이블 | 회원·주문·상품 |
| 속성(attribute) | 칼럼 | 이름·가격 |
| 관계(relationship) | 외래키 | 회원이 주문을 한다 |
| 식별자(identifier) | 기본키 | 회원ID |
엔터티는 *"독립적으로 의미 있는 것"*인지로 판별한다 — 회원·주문은 엔터티지만, 주문의 '상태'는 속성이다(주문 없이 상태만 존재할 이유가 없으므로).
카디널리티 — 관계의 수
관계는 *"한쪽의 한 개체가 다른 쪽 몇 개체와 연결되나"*로 나뉜다. ERD의 까마귀발(crow's foot) 표기에서 ||(정확히 하나)·o{(0개 이상)·|{(1개 이상)가 그 수를 나타낸다.
- 1:1 — 회원 ↔ 프로필. 드물지만 쓸 이유가 있다 — 선택적·민감한 정보를 분리(프로필 사진·상세 정보를 별 테이블로 빼 회원 조회를 가볍게), 또는 접근 권한을 분리. 보통 자주 함께 읽으면 한 테이블, 따로 다루면 1:1 분리다.
- 1:N — 회원 1명이 주문 여러 건(가장 흔함). *N쪽(orders)에 외래키(member_id)*를 둔다.
- N:M — 주문 ↔ 상품(한 주문에 여러 상품, 한 상품이 여러 주문). → 연결 테이블로 푼다.
-- 1:N — N쪽(orders)에 FK
CREATE TABLE orders (
id bigint PRIMARY KEY,
member_id bigint NOT NULL REFERENCES member(id) -- 1명의 회원 : N건의 주문
);
N:M은 연결 테이블로
관계형 DB는 N:M을 직접 표현 못 한다 — 연결(중간) 테이블로 두 개의 1:N으로 분해한다.
CREATE TABLE order_item ( -- N:M 연결 테이블 (orders ↔ product)
order_id bigint REFERENCES orders(id),
product_id bigint REFERENCES product(id),
qty int NOT NULL, -- 관계 자체의 속성
unit_price numeric NOT NULL, -- 주문 '시점'의 가격(상품 가격이 바뀌어도 보존)
PRIMARY KEY (order_id, product_id) -- 복합 PK
);
여기서 연결 테이블이 관계 자체의 속성(수량·주문 시점 가격)을 담는다는 게 핵심이다 — 이런 속성이 생기면 단순 연결을 넘어 의미 있는 엔터티가 된다(주문 항목). 상품의 현재 가격과 주문 당시 가격을 분리해 저장해야 과거 주문 금액이 보존된다.
식별/비식별 관계·약한 엔터티
- 식별 관계 — 부모의 키가 자식의 기본키 일부(order_item의 PK가 order_id를 포함). 자식이 부모 없이 존재할 수 없다(주문 없는 주문 항목은 무의미).
- 비식별 관계 — 외래키지만 자식의 PK는 별도(주문의 member_id는 FK지만 PK는 주문 자기 id). 회원이 사라져도 주문은 자체 정체성이 있다.
- 약한 엔터티(weak entity) — 스스로 식별 못 하고 부모에 의존하는 엔터티(주문 항목). 식별 관계로 모델링된다.
모델링 안티패턴
- EAV(Entity-Attribute-Value) — 속성을 행으로 빼는
(entity, key, value)테이블. "어떤 속성이든 담을 수 있어" 유연해 보이지만 — 조인이 속성 수만큼 폭발하고, 타입 안전·제약·인덱스를 다 잃는다(value가 전부 text). 정말 동적·희소한 속성만 jsonb([08])로 담고, 핵심은 정규 칼럼으로 둔다. - 만능 테이블(God table) — 한 테이블에 수십~수백 칼럼, 대부분 NULL. 여러 엔터티가 뭉친 신호 → 엔터티로 분리([03 정규화]).
- 의미 없는 과한 정규화/반정규화 — 측정 없이 어느 쪽 극단으로도 가지 않는다([03]).
물리 설계로 — 타입·인덱스 결정
논리 모델이 서면 물리 설계에서 *타입(PostgreSQL timestamptz vs MySQL DATETIME)·PK 전략(IDENTITY/AUTO_INCREMENT/UUID, [06])·인덱스([06])·파티션([08])*을 정한다. 이때 예상 데이터량·접근 패턴·읽기/쓰기 비율을 근거로 삼는다 — 같은 논리 모델도 "하루 100건"과 "하루 1억 건"의 물리 설계는 완전히 다르다(후자는 파티셔닝·샤딩까지 고려).
정리 — 그리고 나서 만든다
데이터 모델링의 핵심 — 개념(엔터티·관계, ERD)→논리(테이블·키·정규화)→물리(타입·인덱스, DBMS별) 3단계, 카디널리티(1:1·1:N·N:M)를 정확히(N쪽에 FK, N:M은 연결 테이블), 연결 테이블은 관계 속성을 담는다, EAV·만능 테이블 같은 안티패턴 회피. 테이블을 그리기 전에 관계를 그리는 습관이 나중의 큰 부채를 막는다.
한 줄 요약 — 3단계: 개념(ERD·엔터티/관계, DBMS 무관·이해관계자 합의)→논리(테이블·키·정규화)→물리(타입·인덱스·예상 규모). 카디널리티 1:1(선택/민감 분리)·1:N(N쪽 FK)·N:M(연결 테이블, 관계 속성 포함). 식별/비식별·약한 엔터티. 안티패턴: EAV·만능 테이블.
(출처: Silberschatz DBSC 7e Ch 6 / 김연희 데이터베이스 개론 개체-관계 모델 / PostgreSQL data types.)