SQL 데이터 모델링의 개념 및 특징 : 처음부터 실무까지 한 번에 정리
SQL 데이터 모델링은 데이터베이스에서 데이터를 “저장하기 좋은 형태”로만 만드는 작업이 아니라, 업무가 바뀌어도 쉽게 확장되고, 데이터 품질을 일관되게 유지하며, 조회 성능과 운영 편의성까지 고려해 구조를 설계하는 과정입니다. 이 글에서는 데이터 모델링을 처음 접하는 분도 이해할 수 있도록 핵심 개념을 단순한 예시로 설명하고, 실무에서 바로 적용할 수 있는 체크포인트와 SQL DDL 예시까지 함께 제공합니다.
목차
- 1. SQL 데이터 모델링이란 무엇인가
- 2. 데이터 모델링의 3단계(개념/논리/물리)
- 3. 핵심 구성요소: 엔티티, 속성, 관계, 키
- 4. 데이터 품질을 지키는 장치: 정규화와 제약조건
- 5. 성능과 운영을 좌우하는 물리 설계 포인트
- 6. 예제로 이해하는 모델링 SQL(DDL) 샘플
- 7. 실행 단계: 요구사항부터 검증까지
- 8. 추가로 생각해볼 점(심화)
- 9. 블로그 최적화 정보
핵심 포인트
- 정의: SQL 데이터 모델링은 테이블/컬럼/관계/제약조건/인덱스 등으로 “데이터 구조와 규칙”을 설계하는 일입니다.
- 목표: 데이터 중복·불일치·누락을 줄이고, 업무 변경에 유연하며, 조회/저장 성능과 운영 안정성을 높입니다.
- 핵심 개념: 엔티티(테이블), 속성(컬럼), 관계(FK), 키(PK/UK), 정규화, 제약조건, 인덱스, 카디널리티(1:1/1:N/N:M)입니다.
- 현실적인 균형: 정규화는 품질과 변경 용이성을 높이지만, 조회 성능을 위해 일부 비정규화/요약 테이블이 필요할 때가 있습니다.
- 운영 관점: “모델이 예쁘다”보다 “데이터가 깨지지 않고, 마이그레이션이 가능하며, 장애 시 복구가 쉬운가”가 중요합니다.
1) SQL 데이터 모델링이란 무엇인가
데이터 모델링을 쉽게 말하면, “업무에서 쓰는 개념을 데이터베이스에 옮겨 담는 설계도”를 만드는 일입니다. 예를 들어 쇼핑몰이라면 고객, 주문, 상품 같은 개념이 있고, 이들이 어떻게 연결되는지(고객은 여러 주문을 가진다), 어떤 규칙이 있는지(주문은 반드시 고객에 속한다)를 정의해야 합니다.
SQL에서 모델링이 특히 중요한 이유는, 테이블을 만들고 나면 이후에 바꾸기 어렵기 때문입니다. 구조 변경은 데이터 이관, 애플리케이션 수정, 배포, 다운타임 등 많은 비용을 동반할 수 있어 초기 설계의 품질이 장기 운영 비용을 크게 좌우합니다.
2) 데이터 모델링의 3단계(개념/논리/물리)
학습을 깊게 하려면 “한 번에 SQL부터”가 아니라, 모델링의 층을 이해하는 것이 좋습니다. 보통 다음 3단계로 설명합니다.
| 단계 | 목적 | 산출물/예시 | 주의점 |
|---|---|---|---|
| 개념 모델 | 업무 개념을 큰 덩어리로 정리 | 고객-주문-상품 관계, 주요 용어 정의 | 기술/DBMS 세부사항(타입, 인덱스)은 배제 |
| 논리 모델 | 정규화, 키, 제약을 포함한 구조 확정 | PK/FK/UK, 1:N, 속성 목록, 정규화 | 업무 규칙을 “데이터 규칙”으로 바꾸는 단계 |
| 물리 모델 | DBMS에 맞게 성능/운영까지 반영 | SQL DDL, 인덱스, 파티셔닝, 타입/길이 | 성능만 보고 과도한 비정규화는 장기 비용 증가 |
처음 학습할 때는 “논리 모델”에서 정규화와 키 설계를 제대로 이해하는 것이 가장 큰 지렛대가 됩니다. 이후 “물리 모델”에서 성능과 운영 이슈를 덧붙이면 실무 역량이 빠르게 쌓입니다.
3) 핵심 구성요소: 엔티티, 속성, 관계, 키
엔티티(테이블)와 속성(컬럼)
- 엔티티: 관리해야 하는 대상(고객, 주문, 상품)이며, SQL에서는 보통 테이블로 표현합니다.
- 속성: 엔티티의 특성(고객명, 이메일, 주문일시)이며, SQL에서는 컬럼으로 표현합니다.
속성 정의에서 중요한 것은 “값의 의미와 범위”입니다. 예를 들어 전화번호는 숫자 계산이 목적이 아니므로 정수형보다 문자열이 적합한 경우가 많고, 금액은 부동소수(float)보다 정밀도가 보장되는 타입을 사용하는 편이 안전합니다(DBMS별 권장 타입을 확인하는 습관이 필요합니다).
관계(relationship)와 카디널리티
- 1:1: 한 고객이 하나의 고객상세를 가진다(드문 편, 분리 사유가 있어야 유효)
- 1:N: 한 고객이 여러 주문을 가진다(가장 흔함)
- N:M: 주문은 여러 상품을 포함하고, 상품은 여러 주문에 포함된다(중간 테이블로 분해 필요)
N:M 관계는 SQL에서 바로 표현하기 어렵기 때문에 일반적으로 교차(매핑) 테이블을 둡니다. 예: order_items(주문상품) 테이블을 만들어 주문과 상품을 연결합니다.
키(Key): PK, FK, UK의 역할
- PK(Primary Key): 테이블에서 한 행을 유일하게 식별합니다. 조회/연결/무결성의 중심입니다.
- FK(Foreign Key): 다른 테이블의 PK(또는 UK)를 참조하여 관계를 강제합니다. “존재하지 않는 고객의 주문” 같은 오류를 줄입니다.
- UK(Unique Key): PK가 아니더라도 유일성이 필요한 값(이메일, 사번 등)을 보장합니다.
키 설계는 단순히 “중복을 막는 장치”가 아니라, 업무 규칙을 데이터로 고정하는 장치입니다. 예를 들어 “이메일은 계정에서 유일해야 한다”는 규칙은 애플리케이션 코드보다 DB 제약으로 보장하는 편이 누락/버그에 강합니다.
4) 데이터 품질을 지키는 장치: 정규화와 제약조건
정규화란
정규화는 중복을 줄이고 이상현상(삽입/갱신/삭제 이상)을 방지하기 위해 데이터를 분해하는 방법론입니다. 핵심 방향은 다음과 같습니다.
- 한 컬럼에는 한 의미의 값만 담는다(반복되는 값의 묶음은 분리).
- 부분 종속/이행 종속을 제거해 “한 테이블은 한 주제”에 가깝게 만든다.
- 중복 저장을 줄여 변경 시 불일치 위험을 낮춘다.
다만 “정규화가 무조건 정답”은 아닙니다. 조회 성능이 중요한 화면/리포트에서는 조인 비용이 커질 수 있어, 의도적인 비정규화(요약 테이블, 캐시 테이블, 머터리얼라이즈드 뷰 등)가 필요할 수 있습니다. 중요한 것은 “왜 비정규화가 필요한지”를 설명할 수 있어야 한다는 점입니다.
제약조건(Constraints)으로 규칙을 강제
- NOT NULL: 필수값 보장(예: 주문일시)
- CHECK: 값의 범위/형식 제약(예: 수량 > 0)
- UNIQUE: 유일성 보장(예: 이메일)
- FOREIGN KEY: 참조 무결성 보장(예: 주문은 반드시 고객을 참조)
제약조건을 적절히 쓰면 데이터가 쌓인 뒤에 “왜 이런 값이 들어갔지?” 같은 운영 이슈를 크게 줄일 수 있습니다. 특히 FK는 팀/서비스가 커질수록 가치가 커지지만, 대용량 환경에서는 쓰기 성능과 잠금/삭제 정책까지 같이 검토해야 합니다.
5) 성능과 운영을 좌우하는 물리 설계 포인트
- 인덱스: 자주 조회되는 조건/조인 키에 인덱스를 두되, 쓰기 비용(INSERT/UPDATE 증가)과 균형을 잡습니다.
- 서로게이트 키 vs 내추럴 키: 의미 있는 값(내추럴)을 PK로 쓰면 변경이 어렵고 길이가 커질 수 있습니다. 운영 안정성을 위해 숫자형 대리키를 PK로 두고, 의미값은 UK로 두는 패턴이 흔합니다.
- 시간 컬럼(생성/수정): 운영에서는 “언제 생성/수정되었는지”가 매우 중요합니다. 기본 감사 컬럼을 표준으로 두면 추적과 장애 대응이 쉬워집니다.
- 삭제 정책: 물리 삭제 vs 논리 삭제(soft delete)는 요구사항(감사, 복구, 법적 보관)에 따라 결정합니다.
- 네이밍 규칙: 테이블/컬럼/제약/인덱스 이름을 일관되게 정하면 협업과 자동화(마이그레이션)가 편해집니다.
6) 예제로 이해하는 모델링 SQL(DDL) 샘플
아래 예시는 “고객-주문-주문상품-상품” 구조를 기준으로, PK/FK/UK/CHECK와 인덱스까지 최소 단위로 넣은 예입니다. DBMS마다 문법/타입이 조금씩 다르므로, 학습 목적에서는 구조와 의도를 중심으로 읽으시면 좋습니다.
-- 1) 고객
CREATE TABLE customers (
customer_id BIGINT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
-- 2) 상품
CREATE TABLE products (
product_id BIGINT PRIMARY KEY,
sku VARCHAR(50) NOT NULL UNIQUE,
product_name VARCHAR(200) NOT NULL,
price DECIMAL(12, 2) NOT NULL,
created_at TIMESTAMP NOT NULL
);
-- 3) 주문 (고객 1 : N 주문)
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
customer_id BIGINT NOT NULL,
ordered_at TIMESTAMP NOT NULL,
status VARCHAR(30) NOT NULL,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
-- 4) 주문상품 (주문 N : M 상품을 중간 테이블로 분해)
CREATE TABLE order_items (
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(12, 2) NOT NULL,
CONSTRAINT pk_order_items PRIMARY KEY (order_id, product_id),
CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id) REFERENCES orders(order_id),
CONSTRAINT fk_order_items_product
FOREIGN KEY (product_id) REFERENCES products(product_id),
CONSTRAINT ck_order_items_qty CHECK (quantity > 0)
);
-- 5) 조회 패턴을 고려한 인덱스(예: 고객별 주문 목록)
CREATE INDEX idx_orders_customer_ordered_at ON orders(customer_id, ordered_at);
여기서 데이터 모델링 관점으로 볼 포인트는 다음과 같습니다.
- 관계 강제:
orders.customer_id는 고객을 반드시 참조하도록 FK로 강제합니다. - N:M 해소: 주문과 상품의 관계를
order_items로 풀어내며, (order_id, product_id) 복합 PK로 중복 입력을 막습니다. - 업무 규칙: 수량은 0보다 커야 하므로 CHECK로 규칙을 데이터에 고정합니다.
- 조회 성능: 고객별 주문 조회가 빈번하다면 복합 인덱스가 유효할 수 있습니다(실제 선택은 쿼리와 데이터 분포로 검증).
7) 실행 단계: 요구사항부터 검증까지
데이터 모델링을 “감(感)”으로만 하면 수정 비용이 커집니다. 다음 순서를 습관화하면 설계 품질이 안정적으로 올라갑니다.
- 요구사항 수집: 화면/리포트/저장 이벤트 기준으로 “무엇을 기록해야 하는지”를 정리합니다.
- 용어 사전 만들기: 같은 단어가 다른 의미로 쓰이지 않도록 핵심 용어를 정의합니다.
- 엔티티 도출: 관리 대상(고객/주문/상품 등)을 나열하고 범위를 확정합니다.
- 속성 정의: 컬럼 후보를 모으고, 의미/필수 여부/값의 범위를 명확히 합니다.
- 키 설계: PK/UK 후보를 검토하고, 변경 가능성·길이·검색성을 함께 평가합니다.
- 관계/카디널리티 확정: 1:N, N:M을 명확히 하고 교차 테이블 필요 여부를 결정합니다.
- 정규화 검토: 중복과 이상현상을 점검하고, 필요한 경우 단계적으로 분해합니다.
- 제약조건 설계: NOT NULL, UNIQUE, FK, CHECK로 품질 규칙을 데이터에 반영합니다.
- 물리 설계: 타입/길이, 인덱스, 파티셔닝, 아카이빙(보관) 전략을 고려합니다.
- 검증: 샘플 데이터로 주요 쿼리를 실행하고, 성능과 무결성을 함께 점검합니다.
실무 체크리스트(요약)
| 점검 영역 | 질문 | 권장 액션 |
|---|---|---|
| 무결성 | 잘못된 데이터가 들어갈 통로가 있는가? | NOT NULL/UNIQUE/FK/CHECK로 규칙을 고정 |
| 확장성 | 요구사항이 바뀌면 컬럼/테이블이 안정적으로 늘어나는가? | 정규화 수준 재점검, 용어/도메인 표준화 |
| 성능 | 핵심 조회가 느려질 가능성이 있는가? | 쿼리 패턴 기준 인덱스/요약 구조 검토 |
| 운영 | 장애/감사/복구 시 필요한 정보가 있는가? | created_at/updated_at, 삭제 정책, 이력 전략 정의 |
8) 추가로 생각해볼 점(심화)
- OLTP vs OLAP 모델: 트랜잭션 중심(OLTP)은 정규화가 유리한 경우가 많고, 분석(OLAP)은 스타 스키마/스노우플레이크, 요약 테이블이 중요해집니다.
- 이력(Temporal) 설계: “값이 언제부터 언제까지 유효했는가”를 다뤄야 한다면 유효기간 컬럼(from/to) 또는 이력 테이블 전략이 필요합니다.
- 소프트 삭제(논리 삭제): 삭제된 데이터를 복구/감사해야 한다면
deleted_at같은 컬럼을 두고 조회 조건을 표준화합니다. - 다중 테넌시(Multi-tenant): 고객사별 분리가 필요하면
tenant_id포함, 인덱스 설계, 보안(행 단위 접근)을 함께 고민해야 합니다. - 키 전략: 대리키(BIGINT) + 비즈니스 키(UK) 조합은 변경/통합에 강합니다. 반대로 내추럴 키를 PK로 쓰면 조인 비용/변경 리스크가 커질 수 있습니다.
- 마이그레이션 친화성: 스키마 변경이 잦다면 마이그레이션 도구와 규칙(하위 호환, 단계적 배포)을 전제로 모델을 설계하는 편이 안전합니다.
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

0 댓글