[ERD] 이커머스 ERD 설계 과정: 정규화, 관계 설계, 제약 조건

2025. 7. 18. 11:14·공부일기../DataBase

1. 초기 설계 배경 및 목적

  • 이번 이커머스 프로젝트의 주요 도메인을 기반으로 DB 테이블을 정리하고 관계를 정의하는 ERD를 설계하였다.
  • 초기 목표는 핵심 흐름인 상품 조회 → 주문 → 결제 → 포인트/쿠폰 사용 을 중심으로
    단순하고 명확한 데이터 흐름을 가지도록 구성하는 것이었다.
  • 멋진 이커머스가 목표가아니라 대용량 트래픽 시스템 구현이 목적이기때문에 제약조건을 많이두었다.
    회원가입도 없고 결제 취소, 부분취소 이런것도 없움,, 로그인도 토큰도 없다!

 


2. ERD 작성 규칙

  • 테이블명은 복수형(orders, products 등) 으로 명명
  • 관계는 명확히 주석으로 표현하고, Mermaid 다이어그램을 이용해 시각화
orders ||--o{ order_items : "contains (1:N)"

 


3. 정규화 & 비정규화 과정 사고흐름 !! 

🧩 3-1. 쿠폰 정책 정보 중복 저장 (비정규화)

`coupon_policies` 테이블에는 쿠폰 이름과 할인율이 정의되어 있는데,
이 값을 `coupons` 테이블에도 중복 저장하였다.

  • 이유 1: 사용자가 보유한 쿠폰을 조회할 때, 정책 테이블을 조인하지 않아도 주요 정보를 바로 확인할 수 있도록 하기 위함
  • 이유 2: 정책 변경 이력이 있어도 기존 발급된 쿠폰의 정보는 변경되지 않아야 하므로, 쿠폰 발급 시점의 데이터를 `coupons`에 그대로 남기는 방식으로 설계

→ 조회 효율성과 발급 이력 보존을 위해 일부 비정규화 구조 채택


🧩 3-2. 주문과 주문 항목(order, order_items)에 status 및 ordered_dt 중복 저장 (비정규화)

  • `orders`와 `order_items` 양쪽 모두에 ordered_dt, status 컬럼을 포함함
  • 또한 `order_items`에는 unit_price, total_price를 저장

이유:

  • 인기 상품 조회 시 order_items 기준으로 통계를 내게 되는데, orders의 날짜/status를 함께 조회하려면 조인이 필요함 → 쿼리 복잡도 증가
  • products.price는 시간이 지남에 따라 변경될 수 있으므로, 주문 당시의 가격을 보존하기 위해 unit_price를 저장

→ 쿼리 단순화 및 데이터 정확성 보존을 위한 비정규화

 


4. 관계 설정 

🧩 4-1. 1:1 vs 1:N 관계 고민 – 결제 정보

  • `orders` 와 `order_payments` 는 현재 1:1 관계로 설정
  • 실제로는 부분 결제, 재결제 등으로 인해 1:N이 될 수도 있지만,
    이번 프로젝트에서는 결제취소는 존재하지 않는다고 가정하고 설계

확장 고려사항:

  • 향후 1:N으로 확장 시, `order_payments`에 is_last 와 같은 컬럼을 추가하여 마지막 결제 여부를 명시하면 정합성 유지 가능

🧩 4-2. 쿠폰 테이블 설계 최소화

쿠폰 도메인은 실제로는 다음과 같이 구성 할 수있다고 생각했다

  1. `coupon_policies`: 쿠폰 정책 정의
  2. `coupons`: 사용자 발급 이력
  3. `coupon_events`: 선착순 이벤트 발급 등 쿠폰을 발급하는 행위 기록

하지만 이번 프로젝트에서는 최소 구성을 위해 1, 2번 테이블만 사용,
이벤트 로직은 서비스 계층에서 구현하도록 설계했다.


🧩 4-3. 외부연동 테이블 설계안

외부 연동은 실제 운영 환경에서 다음과 같은 시나리오로 구성될 수 있다고 판단했다.

  • `order_external_events`: 외부 시스템(PG사, 배송 시스템 등)과의 비동기 연동 요청/응답 로그
  • `external_failures`: 연동 실패 내역 및 재시도 이력 저장 테이블

주문이 완료된 후 배송 요청, 결제 결과 확인 등의 처리를 위해
외부 시스템과 비동기 통신을 수행해야 하며, 이때 성공/실패 여부를 별도로 기록해야 할 필요가 있다.

하지만 이번 프로젝트는 내부 흐름 구조와 대용량 처리에 집중하는 것이 목적이므로,
외부 연동 로직은 서비스 계층의 책임으로 처리하는 것으로 가정하고, 테이블은 설계에서 제외하였다.

 



📌 정리

  • 중복 저장 여부는 “데이터 보존”과 “조회 효율”을 기준으로 판단
  • 관계 설정은 현재 스펙에 맞춰 단순화하고, 확장 가능성은 주석/구조로 대비
  • 불필요한 정규화는 피하고, 실제로 운영 가능한 구조를 우선시함

 


5. 제약 조건 설정

모든 테이블에는 식별을 위한 PRIMARY KEY를 설정했고,
관계가 있는 테이블 간에는 FOREIGN KEY를 통해 참조 무결성을 명확히 지정했다.
특히 쿠폰의 경우, 동일 쿠폰 정책이 한 사용자에게 중복 발급되지 않도록
UNIQUE(user_id, coupon_policy_id) 제약 조건을 추가했다.

 

제약조건  적용 컬럼  이유
`PRIMARY KEY` 모든 테이블의 식별자(*_id) 각 레코드 고유 식별을 위한 기본 조건
`FOREIGN KEY` user_id, product_id, coupon_policy_id 등 테이블 간 관계 및 참조 무결성 보장
`UNIQUE(user_id, coupon_policy_id)` coupons 동일한 쿠폰 정책은 한 사용자당 한 번만 발급 가능하도록 제약
`NOT NULL` 모든 PK / 필수 값들 필수 데이터가 누락되지 않도록 방지
 

 


6. ERD 이미지 (Mermaid)

 `orders` -  `order_payments` 실무에서는 부분취소등 `1:N` 관계로 설정해야할거같지만 나는 부분취소 결제취소같은 부분이없이 대용량 분산처리 시스템에집중하기위해 제약조건을 꽤나 걸어서 `1:1`관계를 설정했다.

머메이드 다이어그램은 직접 그리지않아도 코드 작성으로 해결하면되서 너무 좋았다!!

 

ERD

관계 설명!

더보기

관계 설명
`users -- orders`           : 사용자 1명은 여러 주문을 할 수 있음 (1:N)
`users -- point_histories` : 사용자 1명은 여러 포인트 이력을 가질 수 있음 (1:N)
`users -- coupon_policies` : 사용자 1명은 각 쿠폰 정책당 최대 1개의 쿠폰만 가질 수 있음 (1:1)
`users -- coupons` : 사용자 1명은 여러 쿠폰을 보유할 수 있지만 쿠폰 정책별로 1개의 쿠폰만 가질 수 있음 (1:N, UNIQUE(user_id, coupon_policy_id))
`coupon_policies -- coupons`: 쿠폰 정책 1개는 여러 쿠폰을 발급함 (1:N)
`orders -- order_items`     : 주문 1건에 여러 주문 항목이 포함됨 (1:N)
products -- order_items`   : 상품 1개가 여러 주문 항목에 포함될 수 있음 (1:N)
`orders -- order_payments`  : 주문 1건은 결제 정보 1건과 연결됨 (1:1)
`coupons -- order_payments` : 쿠폰 1개는 최대 1건의 결제에 사용될 수 있음 (0..1:N)

 

부모 테이블 자식 테이블 관계  유형 설명
users orders 1:N 한 사용자는 여러 주문 가능
users point_histories 1:N 한 사용자의 여러 포인트 이력
users coupons 1:N 한 사용자가 여러 쿠폰 가질 수 있음
coupon_policies coupons 1:N 하나의 정책에서 여러 쿠폰 발급 가능
orders order_items 1:N 하나의 주문에 여러 상품 포함 가능
products order_items 1:N 하나의 상품이 여러 주문에서 사용될 수 있음
orders order_payments 1:1 한 주문에 결제 정보 하나 (1:1 관계)
coupons order_payments 1:N or 0:1 하나의 쿠폰이 주문 결제 시 사용될 수 있음 (사용 안 될 수도 있음)

 

 

 


머메이드 다이어그램 링크

https://mermaid.js.org/

 

Mermaid

Create diagrams and visualizations using text and code.

mermaid.js.org

 

'공부일기.. > DataBase' 카테고리의 다른 글

[인덱스 용어정리] 클러스터 인덱스 ,보조인덱스, 단일 인덱스,복합 인덱스, 커버링 인덱스  (1) 2025.07.27
[DB 인덱스 개념 정리] B+Tree, 클러스터드 인덱스, 카디널리티, 커버링 인덱스, 디스크와 메모리  (2) 2025.07.27
[MySql] 컬럼 일련번호 자동 증가 AUTO_INCREMENT  (0) 2025.06.20
[DBeaver] MySQL 연결 시 "Unknown database '데이터베이스이름' " 해결하기 / Database 이름 찾기/생성하기 - MacOS  (0) 2025.06.20
[DBeaver] MySQL 연결 시 "Public Key Retrieval is not allowed" 해결하기  (0) 2025.06.20
'공부일기../DataBase' 카테고리의 다른 글
  • [인덱스 용어정리] 클러스터 인덱스 ,보조인덱스, 단일 인덱스,복합 인덱스, 커버링 인덱스
  • [DB 인덱스 개념 정리] B+Tree, 클러스터드 인덱스, 카디널리티, 커버링 인덱스, 디스크와 메모리
  • [MySql] 컬럼 일련번호 자동 증가 AUTO_INCREMENT
  • [DBeaver] MySQL 연결 시 "Unknown database '데이터베이스이름' " 해결하기 / Database 이름 찾기/생성하기 - MacOS
s0-0mzzang
s0-0mzzang
공부한것을 기록합니다...
  • s0-0mzzang
    승민이의..개발일기..🐰
    s0-0mzzang
  • 전체
    오늘
    어제
    • 전체~ (108)
      • 마음가짐..! (10)
      • 공부일기.. (76)
        • weekly-log (6)
        • Spring (19)
        • Java (18)
        • DataBase (10)
        • git (2)
        • JPA (6)
        • kafka (1)
        • Backend Architecture (3)
        • Troubleshooting (삽질..ㅋ) (2)
        • Cloud (1)
        • Docker (2)
        • 알고리즘 (1)
        • 리액트 (2)
        • Infra (3)
      • 하루일기.. (22)
        • 그림일기 (8)
        • 생각일기 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    StringTokenizer
    스프링부트
    다짐
    항해99
    TDD
    항해플러스
    ERD
    리팩토링
    MySQL
    SpringBoot
    JPA
    인프라 기초
    ADC 환경
    BufferedReader
    자바
    spring boot
    spring
    React
    swagger
    Paging
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
s0-0mzzang
[ERD] 이커머스 ERD 설계 과정: 정규화, 관계 설계, 제약 조건
상단으로

티스토리툴바