ERD는 순서만 알면 생각보다 금방 작성할 수 있습니다. 요구사항에서 개체를 뽑고, 개체 사이의 관계를 정하고, 키를 확정한 다음, 표기법에 맞춰 옮기면 끝입니다. 과제로 처음 ERD를 그려야 하는 분이든, 실무에서 첫 설계를 맡은 분이든 이 순서는 같습니다.
말로만 들으면 뻔하니 게시판 DB 하나를 잡고 처음부터 끝까지 같이 그려보죠. 글 마지막에는 그대로 실행되는 DDL과, 설계를 스스로 점검할 수 있는 체크리스트도 준비되어 있습니다.
1단계: 요구사항에서 개체 뽑기
막막할 때는 요구사항 문장에서 명사에 동그라미부터 치면 됩니다.
회원이 게시글을 작성한다. 게시글에는 댓글이 달린다. 게시글은 하나의 카테고리에 속한다.
회원, 게시글, 댓글, 카테고리. 이 네 가지가 개체 후보입니다.
물론 명사라고 다 개체가 되는 건 아닙니다. 기준은 하나만 기억하면 됩니다. 여러 건을 저장하고 각각을 구분해야 하면 개체, 무언가를 설명하는 값 하나면 속성입니다.
요구사항에서 나올 법한 명사들을 이 기준으로 분류해 보면 이렇게 됩니다.
| 명사 | 판정 | 이유 |
|---|---|---|
| 회원, 게시글, 댓글 | 개체 | 여러 건이 쌓이고 각각을 식별해야 함 |
| 카테고리 | 개체 | 목록이 따로 관리되고 여러 게시글이 공유함 |
| 제목, 작성일 | 속성 | 게시글 하나를 설명하는 값 |
| 댓글 수 | 둘 다 아님 | 세면 나오는 집계값 — 저장하는 순간 정합성 관리 대상 |
'댓글 수'처럼 계산으로 얻을 수 있는 값은 일단 빼는 게 원칙입니다. 성능 때문에 저장하는 반정규화는 필요가 증명된 뒤에 해도 늦지 않습니다.
'관리자' 같은 역할도 고민되는 지점입니다. 역할 종류가 몇 개 안 되고 딸린 정보가 없다면 회원의 role 속성으로 충분하고, 역할마다 권한 목록 같은 데이터가 붙기 시작하면 그때 개체로 승격하면 됩니다.
설계 리뷰를 하다 보면 가장 자주 보이는 실수가 바로 이 지점입니다. 카테고리를 개체로 분리하지 않고 게시글 테이블에 category VARCHAR(50)로 박아버리는 식인데, 처음엔 잘 돌아가니까 문제를 못 느낍니다.
그러다 카테고리 이름을 하나 바꾸는 날, 게시글 수만 건을 UPDATE하면서 깨닫게 됩니다. 여러 행이 같은 값을 공유하고 있다면 분리하라는 신호로 받아들이는 게 안전합니다.
2단계: 관계와 카디널리티 정하기
개체를 뽑았으면 이제 동사 차례입니다. "작성한다", "달린다", "속한다"가 전부 관계가 됩니다. 관계마다 양쪽에 몇 건이 대응하는지만 정하면 됩니다.
- 회원 한 명이 게시글을 여러 개 쓴다 → 회원 1 : N 게시글
- 게시글 하나에 댓글이 여러 개 달린다 → 게시글 1 : N 댓글
- 카테고리 하나에 게시글이 여러 개 속한다 → 카테고리 1 : N 게시글
게시글과 태그처럼 양쪽 다 여러 개면 이야기가 달라집니다. 관계형 DB는 N:M을 직접 저장하지 못하기 때문에, post_tags 같은 중간 테이블을 하나 두고 1:N 두 개로 쪼갭니다. 면접 단골 질문이기도 하니 요령만이 아니라 이유까지 기억해 두는 편이 좋습니다.
놓치기 쉬운 건 최소값입니다. 댓글이 하나도 없는 게시글도 있어야 하니까, 게시글 입장에서 댓글은 0..N입니다. 사소해 보여도 이 차이가 나중에 NOT NULL을 걸지, LEFT JOIN을 쓸지를 가릅니다.
한 걸음 더: 자주 만나는 세 가지 변형
1:1 관계. 회원과 회원 프로필처럼 하나에 정확히 하나만 대응하는 관계도 있습니다. 로그인에 매번 쓰이는 정보와 가끔 조회되는 소개글·설정을 분리하거나, 민감한 정보를 접근 권한이 다른 테이블로 떼어낼 때 나옵니다. 다만 다이어그램에 1:1이 유난히 많다면, 하나여도 될 테이블을 쪼갠 건 아닌지 먼저 의심해 보세요.
속성이 붙는 N:M. post_tags에 '태그를 단 시각', '태그를 단 사람'이 붙는 순간, 중간 테이블은 단순 연결이 아니라 자기 의미를 가진 개체가 됩니다. 이런 경우에는 이름도 관계 나열형이 아니라 의미가 드러나는 쪽으로 지어주는 게 좋습니다.
자기참조 관계. 대댓글이 대표적입니다. comments에 parent_id를 추가해 자기 자신을 참조하게 하면 되고, 최상위 댓글은 부모가 없으니 NULL을 허용합니다. 이 글의 실습에서는 단순함을 위해 뺐지만, 실무 게시판이라면 거의 반드시 만나게 되는 구조입니다.
3단계: 속성과 키 확정
기본키는 고민할 것 없이 id 같은 대리키를 권합니다. 이메일을 기본키로 쓰면 어떻게 되냐면, 회원이 이메일을 바꾸는 순간 그 값을 참조하던 모든 곳이 같이 흔들립니다.
"주민번호나 ISBN처럼 절대 안 바뀌는 자연키면 되지 않느냐"는 반론도 있지만, 실무에서 '절대'는 자주 깨집니다. 이메일에는 UNIQUE 제약만 걸어두면 중복 방지라는 실익은 그대로 챙길 수 있습니다.
외래키 위치는 기계적으로 정해집니다. 항상 관계의 N쪽입니다. 회원 1 : N 게시글이면 게시글 테이블이 member_id를 갖습니다.
중간 테이블의 기본키는 두 갈래가 있습니다. (post_id, tag_id) 복합 기본키로 가면 같은 조합의 중복이 구조적으로 차단되고, 별도 id를 두고 두 컬럼에 UNIQUE를 걸면 다른 테이블에서 이 행을 참조하기 쉬워집니다. 어느 쪽이든 틀린 건 아니고, 중간 테이블을 다시 참조할 일이 있느냐로 정하면 됩니다.
자료형은 지금 완벽하게 정하려고 애쓰지 않아도 됩니다. 설계가 한 바퀴 돌고 나서 다듬는 편이 결과적으로 빠릅니다.
삭제 정책까지 정해야 설계가 끝난다
관계를 그렸다면 관계마다 한 번씩 물어봐야 하는 질문이 있습니다. "부모가 지워지면 자식은 어떻게 되는가?"
회원이 탈퇴하면 그 회원의 게시글과 댓글은 같이 사라져야 할까요? ON DELETE CASCADE를 무심코 걸었다가 회원 한 명을 지웠는데 게시글과 댓글이 연쇄로 사라지는 사고는 드물지 않습니다.
기본은 RESTRICT로 막아두고, 정말 같이 지워져야 하는 관계에만 CASCADE를 명시하는 편이 안전합니다. 게시판이라면 회원 행을 지우는 대신 비활성 상태로 바꾸는 소프트 삭제도 흔한 선택지입니다.
IE(까마귀발) 표기법 읽는 법
표기법 이름이 여러 개 나와서 겁먹기 쉬운데, 실무에서 마주치는 건 거의 IE(Information Engineering) 하나입니다. 선 끝의 기호가 카디널리티입니다.
|— 정확히 1○— 0 (없어도 됨)- 까마귀발(세 갈래) — N (여러 건)
실제 선 끝에는 이 기호가 두 개씩 조합됩니다. |○는 "0 또는 1", |<는 "1 이상", ○<는 "0 이상"으로 읽습니다.
우리 게시판으로 연습해 보면 이렇게 됩니다.
- 회원 ─ 게시글: 회원 쪽 끝은
||, 게시글 쪽 끝은○<. "게시글은 반드시 회원 한 명의 것이고, 회원은 게시글이 없을 수도 여러 개일 수도 있다"로 읽습니다. - 카테고리 ─ 게시글: 같은 구조입니다. "게시글은 반드시 카테고리 하나에 속한다"가 됩니다.
실선은 식별 관계, 점선은 비식별 관계를 나타내는데, 처음에는 이 구분보다 까마귀발 방향을 정확히 읽는 게 먼저입니다. 참고로 과제에서는 마름모로 관계를 그리는 Chen 표기법을 요구하기도 하고, Oracle 계열 문서에서는 Barker 표기법이 보이기도 합니다. 기호 4가지 전체와 세 표기법의 비교는 ERD 표기법 총정리에 읽기 연습과 함께 정리해 두었습니다.
이름은 두 벌로 관리하는 습관도 이 단계에서 들여둘 만합니다. 사람이 읽는 논리명(회원, 게시글)과 DB에 들어가는 물리명(members, posts)을 함께 적어두면 기획자와 개발자가 같은 그림을 놓고 대화할 수 있습니다. ERD 도구들이 Logical/Physical 뷰를 나눠 제공하는 이유이기도 합니다.
실습: 게시판 DB를 DDL로
위에서 정한 내용을 MySQL DDL로 옮기면 이렇게 됩니다. 복사해서 바로 실행해도 됩니다.
CREATE TABLE members (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
nickname VARCHAR(50) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE posts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
member_id BIGINT NOT NULL,
category_id INT NOT NULL,
title VARCHAR(200) NOT NULL,
content MEDIUMTEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_posts_member FOREIGN KEY (member_id) REFERENCES members (id),
CONSTRAINT fk_posts_category FOREIGN KEY (category_id) REFERENCES categories (id)
);
CREATE TABLE comments (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
post_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
content TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_comments_post FOREIGN KEY (post_id) REFERENCES posts (id),
CONSTRAINT fk_comments_member FOREIGN KEY (member_id) REFERENCES members (id)
);
이 DDL에는 몇 가지 의도적인 선택이 들어 있습니다.
- 게시글·댓글의
id는 BIGINT, 카테고리는 INT입니다. 쌓이는 속도가 다른 테이블에 같은 크기를 줄 이유가 없습니다. - 본문은
MEDIUMTEXT입니다. TEXT의 64KB 한도는 긴 글에서는 금방 넘어갑니다. 댓글은 TEXT면 충분합니다. created_at DEFAULT CURRENT_TIMESTAMP를 걸어, 애플리케이션이 깜빡해도 기록은 남게 했습니다.- MySQL(InnoDB)은 외래키 컬럼에 인덱스를 자동으로 만들어 주므로,
member_id로 게시글을 찾는 흔한 조회는 별도 작업 없이도 인덱스를 탑니다.
posts.member_id가 "회원 1 : N 게시글"의 N쪽 외래키입니다. 2단계에서 정한 관계와 외래키 위치가 하나라도 어긋난다면, 설계를 한 게 아니라 그림을 그린 겁니다.
넘어가기 전 자가 점검 체크리스트
설계를 마쳤다면 다이어그램을 놓고 이것만 확인해 보세요. 리뷰어가 없어도 이 여섯 가지 유형이면 큰 사고는 면할 수 있습니다.
- 모든 테이블에 기본키가 있는가
- 외래키가 전부 관계의 N쪽에 있는가
- N:M이 중간 테이블 없이 남아 있진 않은가 (한 컬럼에 값을 콤마로 이어 넣고 있다면 위험 신호)
- 세면 나오는 값을 저장하고 있진 않은가
- 이름 규칙이 하나로 통일돼 있는가 (단수/복수, 대소문자, 구분자)
- 관계마다 삭제 정책을 정했는가
도구는 무엇으로 그릴까
화이트보드로 시작하는 건 좋은 방법입니다. 문제는 그다음입니다.
범용 드로잉 툴로 계속 관리하면 그림과 실제 스키마가 조금씩 어긋나고, 어느 순간부터 아무도 그림을 믿지 않게 됩니다. 그래서 컬럼끼리 직접 연결되고 DDL로 오갈 수 있는 전용 도구가 결국 편합니다. 팀과 공유하고 리뷰받는 것까지 생각하면 더더욱 그렇습니다.
위 실습처럼 DDL이 이미 손에 있다면 WorksCove ERD에 붙여넣기만 해도 게시판 다이어그램이 바로 만들어집니다.

이 도구를 만들기 전에는 저희도 드로잉 툴과 엑셀 명세서를 오가며 이중으로 관리했습니다. 릴리스가 몇 번 지나자 그림이 실제 스키마와 맞지 않는 부분이 하나둘 생겼고, 회의 때마다 "그림 말고 DB를 보자"는 말이 나오는 지경이 됐습니다.
다이어그램이 스키마 그 자체가 아니면 언젠가 반드시 어긋난다. WorksCove ERD를 DDL 기반으로 만든 이유입니다.
자주 묻는 질문
표기법은 어떤 것을 쓰는 게 좋나요?
실무에서는 IE(까마귀발) 표기법이 사실상 표준입니다. 과제에서 Chen 표기법을 지정한 경우가 아니라면 IE 표기법으로 그리는 것을 권합니다.
N:M 관계는 어떻게 그리나요?
관계형 데이터베이스는 N:M을 직접 저장할 수 없습니다. 두 테이블의 기본키를 외래키로 갖는 중간 테이블을 만들고, 1:N 관계 두 개로 풀어서 그립니다.
테이블 이름은 단수형과 복수형 중 무엇이 좋나요?
정답이 정해져 있다기보다 팀 안에서 통일하는 것이 중요합니다. 복수형(members, posts)은 user 같은 예약어와 충돌할 확률이 낮아 실무에서 조금 더 흔하게 쓰입니다. 어느 쪽이든 한 프로젝트 안에서 섞어 쓰지만 않으면 됩니다.
이미 운영 중인 DB가 있는데 ERD를 처음부터 그려야 하나요?
그릴 필요 없습니다. DDL을 내보내 ERD 도구에 붙여넣으면 다이어그램이 자동으로 생성되고, 리버스 엔지니어링을 지원하는 도구라면 DB에 직접 연결해 가져올 수도 있습니다.
순서를 다시 줄이면 개체 → 관계 → 키 → 표기입니다. 게시판 예제로 감을 잡았다면, 남은 건 지금 진행 중인 프로젝트의 요구사항으로 같은 과정을 한 번 더 돌려보는 것뿐입니다.
그다음이 궁금하다면 테이블을 어디까지 쪼갤지 정하는 정규화, 그리고 완성된 스키마를 문서로 만드는 테이블 명세서 자동화가 자연스러운 다음 주제입니다.