![[DB 모델링] 쉬운 데이터베이스 모델링](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvoAv0%2FbtsNdBFikbf%2FTw8hMIfNPrD3dkk8zf82B0%2Fimg.png)
이 글은 인프런의 지식 공유자 박재성님의 강의를 듣고 개인적으로 정리하는 글임을 알립니다.
가장 중요한 핵심 원칙
DB 설계의 핵심은 '중복 없애기'이다.
users 테이블
id | 이름 |
1 | 박재성 |
2 | 김유찬 |
posts 테이블
id | 제목 | 내용 | 작성자 |
1 | [속보] 개발자 채용 확대 | 내용1 | 박재성 |
2 | 취업 꿀팁 공유합니다! | 내용2 | 박재성 |
... | ... | ... | ... |
132 | 어제 면접 보고 온 썰 | 내용132 | 박재성 |
위의 표와 같이 게시글 데이터가 DB에 많이 쌓여있다고 가정하자. 어느 날 박재성이라는 사람이 박지성으로 개명을 했다.
그래서 게시글에 작성된 작성자의 이름도 전부 박재성에서 박지성으로 고쳐주어야 한다. 그럼 위 게시글의 모든 데이터를 뒤져서 박재성이라는 이름을 전부 찾아서 박지성으로 고쳐야 한다.
이런 문제가 발생한 가장 근본적인 이유는 데이터 중복 때문이다.
중복되는 데이터가 많으면 많을수로 무언가를 수정할 때 중복되는 모든 데이터를 찾아서 고쳐야 한다.
이 때, 실수로 중복된 데이터를 누락해서 수정을 하지 않는 경우가 발생하고, 이렇게 되면 데이터들 사이에 모순이 생겨버린다.
이상 현상
저장된 데이터들 중에서 모순되는 상황이 생긴 현상을 보고 이상현상(Anomaly)라고 얘기한다.
위의 경우는 이상현상(Anomaly) 중 갱신 이상(Update Anomaly)에 해당한다.
이런 문제를 해결하기 위해 만들어낸 DB 설계 방법이 정규화(Normalization)다.
DB 설계 규칙
규칙 1
한 칸에는 한 가지 정보만 들어가도록 만들어라
- 데이터베이스 테이블을 설계할 때 ‘한 칸에는 한 가지 정보만 들어가야 한다.’라는 규칙을 지켜야 한다.
- 이 규칙이 뭔지 알아보기 위해 이 규칙을 안 지킨 사례 먼저 살펴보자.
users 테이블
id(PK) | 이름 | 이메일 |
1 | 박재성 | js123@naver.com, js00@naver.com |
2 | 김시안 | king123@naver.com, queen123@naver.com |
- 조회해서 사용할 때마다 중간에 있는 콤마(,)를 제거하고 배열에 집어넣는 로직을 넣어야 한다.
- 삽입할 때든 삭제할 때든 항상 콤마(,)를 신경써서 작업해야 한다.
- 그리고 한 칸에 데이터를 여러개 집어넣다보면 실수로 데이터를 중복해서 넣어버리기도 한다.
- 따라서 한 칸에는 한 가지의 정보만 넣으려고 하는 것이다.
한 칸에 2가지 이상의 정보가 들어가있을 때는 테이블을 분리하면 된다. 테이블을 분리함으로써 한 칸에 한 가지 정보만 들어가도록 해야 한다.
users 테이블
id(PK) | 이름 |
1 | 박재성 |
2 | 김시안 |
emails 테이블
id(PK) | 이메일 | 사용자 id(FK) |
1 | js123@naver.com | 1 |
2 | js00@naver.com | 1 |
3 | king123@naver.com | 2 |
4 | queen123@naver.com | 2 |
- 위와 같이 데이터를 저장하니 특정 사용자의 이메일 주소도 알 수 있고, 한 칸에 한 가지의 정보만 들어가게 만들었다.
- 위와 같이 테이블을 분리해야 한다.
- 한 칸에 두 가지 이상의 정보가 들어가있을 땐, 테이블을 분리해서 FK를 활용해 한 칸에 한 가지의 정보만 들어가게 만들어야 한다.
이 과정을 데이터베이스 이론에서는 제1정규형이라고 부른다.
규칙 2
헷갈릴 땐 관계(1:1, 1:N, N:M)를 파악해라
- 개발자들이 수많은 DB 설계 케이스를 접하다보니 엔티티 간의 관계에서 패턴을 찾은 것이다.
- 그게 바로 1:1 관계, 1:N 관계, N:M 관계라는 패턴이다. 이 개념을 알게 되면 DB 설계 할 때 조금 더 수월하게 할 수 있다.
엔티티 관계 파악 방법
엔티티 간에 어울리는 동사찾기
- A(주어)가 B를 _____. (A가 주어)
-> 사용자가 이메일을 소유한다. - B(주어)가 A에 의해 ______. (B가 주어)
-> 이메일은 사용자에 의해 소유된다.
서비스의 관점에서 동사를 떠올려야 한다.
이전 과정에서 찾은 동사를 활용해 적절한 단어(하나의 or 여러개의) 찾기
- 하나의 A는 (하나의 or 여러 개의) B를 _______. (A의 관점)
-> 한 명의 사용자는 여러개의 이메일을 소유한다.(소유할 수 있다.) - 하나의 B는 (하나의 or 여러 개의) A에 의해 _______. (B의 관점)
-> 하나의 이메일은 한 명의 사용자에 의해 소유된다.
문장 처음에 시작하는 하나의라는 말을 반드시 붙여야 헷갈리지 않는다.
서비스를 어떻게 기획하냐에 따라 달라질 수 있다. 반드시 자신의 서비스에 대입해서 생각해야 한다.
관계 파악하기
- A, B의 관점 전부 다 하나만 기진다면 → A : B = 1 : 1
- A의 관점에서는 여러개의 B를 가지고, B의 관점에서는 하나의 A를 가진다면 → A : B = 1 : N
- A의 관점에서는 하나의 B를 가지고, B의 관점에서는 여러개의 A를 가진다면 → A : B = N : 1
- A, B의 관점 전부 다 여러개를 가진다면 → A : B = N : M
따라서 예시에서 사용된 사용자와 이메일의 관계는 1:N 관계이다.
관계간 특징
1:N 관계의 특징
- N쪽의 테이블에 FK가 들어가야 한다.
N:M 관계의 특징
- 중간 테이블이 있어야 한다.
- 중간 테이블에 두 테이블의 FK가 들어가야 한다.
- N:M 관계에서 중간 테이블을 추가해 1:N 관계로 바꿔 표현하게 된다.
1:1 관계의 특징
- 아무 테이블에 FK를 넣어도 된다.
- 합쳐도 되는 지 고려해봐야 한다. (어지간하면 1:1 관계로 분리하지 않는 것이 좋다.)
규칙3
N:M 관계에서는 중간 테이블을 만들어라
- 학생 테이블이 있고 수강 과목 테이블이 있다고 가정하면, 수강 과목은 국어, 수학, 과학 이렇게 3가지 과목이 있다.
- 학생들 입장에서는 국어, 수학, 과학 3가지 과목을 들을 수 있고, 반대로 수강 과목 입장에서도 각 과목들은 모든 학생들에게 선택될 수 있다.
- 이는 새로운 중간 테이블을 하나 만들어야 한다.
students 테이블
id | 이름 |
1 | 박재성 |
2 | 김재은 |
3 | 김지훈 |
course_registrations 테이블(중간 테이블)
id | 학생 id(FK) | 수강 과목 id(FK) |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 2 | 1 |
4 | 2 | 2 |
5 | 3 | 1 |
6 | 3 | 3 |
courses 테이블
id | 과목명 |
1 | 국어 |
2 | 수학 |
3 | 과학 |
규칙4
데이터 중복이 발생하는 지 시뮬레이션을 돌려봐라.
- DB 설계의 핵심 원칙은 중복을 없애는 것이다.
- 초안으로 구성된 테이블에서 데이터 중복이 발생하는 구성인지 임의로 데이터를 넣어봐야 한다.
posts 테이블
id | 제목 | 내용 | 작성자 |
- 위 테이블을 잘 만든건지 확인을 해봐야 한다.
- DB 설계에서 중복 데이터만 발생하지 않는다면 잘 설계됐다고 판단할 수 있다.
- 따라서 아래 테이블에 임의의 데이터를 여러개 넣어볼 것이다.
posts 테이블
id | 제목 | 내용 | 작성자 |
1 | [속보] 개발자 채용 확대 | 내용1 | 박재성 |
2 | 취업 꿀팁 공유합니다! | 내용2 | 박재성 |
3 | 어제 면접 보고 온 썰 | 내용3 | 박재성 |
- 데이터를 넣다보니 작성자 컬럼에서 중복 데이터가 발생했다.
- 데이터 중복이 발생하면 안 좋은 점이 박재성이라는 이름을 개명했을 때 전부 일일이 찾아서 고쳐야 한다는 불편한 점이 있다고 했다.
- 그리고 그 과정에서 실수가 발생할 수도 있다고 했다. 따라서 데이터 중복이 발생하지 않게 설계해야 한다.
특정 컬럼에서 데이터 중복이 발생했다면 테이블 분리로 해결하면 된다.
테이블을 분리해서 아래와 같이 구조를 바꿨더니 데이터 중복이 없어졌다. 이 구조에서는 박재성이라는 이름을 개명할 때 users 테이블에 있는 딱 1개의 데이터만 수정하면 된다. 그러면 게시글에서 표시되는 작성자의 이름은 전부 한 번에 실수 없이 바뀐다.
posts 테이블
id | 제목 | 내용 | 작성자 id (FK) |
1 | [속보] 개발자 채용 확대 | 내용1 | 1 |
2 | 취업 꿀팁 공유합니다! | 내용2 | 1 |
3 | 어제 면접 보고 온 썰 | 내용3 | 1 |
users 테이블
id | 이름 |
1 | 박재성 |
이 과정을 데이터베이스 이론에서는 제2정규형이라고 부른다.
제3정규형은 아래와 같다.
테이블 안에서 “A → B → C”처럼 중간 단계가 있으면, B-C 관계를 따로 빼야 한다.
users 테이블
"부서"가 같으면 전화번호도 같다. → 이건 "부서 → 부서전화번호" 이므로 이행적 종속이다.
id 이름 부서 부서 전화번호 1 철수 개발팀 02-1234-5678 2 영희 개발팀 02-1234-5678
따라서 아래와 같이 정규화 작업을 해야한다.
users 테이블
id 이름 부서 id (FK) 1 철수 1 2 영희 1
departments 테이블
id 부서명 전화번호 1 개발팀 02-1234-5678
규칙5
가짜 중복과 진짜 중복을 구별해라
posts 테이블
id | 제목 | 내용 | 작성자 |
1 | [속보] 개발자 채용 확대 | 내용 | 박재성 |
2 | [속보] 개발자 채용 확대 | 내용 | 박재성 |
3 | [속보] 개발자 채용 확대 | 내용 | 박재성 |
- 박재성이라는 사용자가 같은 제목과 같은 내용의 게시글을 3개 작성했다고 가정하자.
- 겉으로만 봤을 땐 제목 컬럼에서도 데이터 중복이 발생한 것처럼 보이고, 내용 컬럼에서도 데이터 중복이 발생한 것처럼 보이고, 작성자 컬럼에서도 데이터 중복이 발생한 것처럼 보인다.
- 하지만 여기서 진짜 데이터 중복이 발생한 컬럼은 작성자 컬럼뿐이다.
가짜 중복인지 진짜 중복인지 판단하려면 이렇게 질문해보면 된다.
“실제 서비스에서 A 데이터의 값을 수정하면, B 데이터의 값도 같이 수정되어야 하는가?”
- 1번 게시글의 작성자 이름을 수정하면, 2번 게시글의 작성자 이름도 같이 수정되어야 하는가?
→ Yes! (= 진짜 중복이다) - 1번 게시글의 제목을 수정하면, 2번 게시글의 제목도 같이 수정되어야 하는가?
→ No! (= 가짜 중복이다) - 1번 게시글의 내용을 수정하면, 2번 게시글의 내용도 같이 수정되어야 하는가?
→ No! (= 가짜 중복이다)
진짜 중복을 찾아냈다면 진짜 중복의 컬럼에 대해서만 테이블을 분리해야한다.
예시를 하나 더 들면 아래와 같다.
products 테이블
id | 상품명 | 카테고리 |
1 | 락스 | 생활용품 |
2 | 락스 | 생활용품 |
3 | 락스 | 생활용품 |
- 1번 상품의 카테고리명을 수정하면, 2번 상품의 카테고리명도 수정되어야 하는가?
→ Yes! (= 진짜 중복이다) - 1번 상품의 상품명을 수정하면, 2번 상품의 상품명도 수정되어야 하는가?
→ No! (= 가짜 중복이다)
규칙6
숨어있는 중복을 찾아라
posts 테이블
id | 제목 | 내용 | 좋아요 수 | 사용자 id (FK) |
1 | 제목1 | 내용1 | 2 | 1 |
users 테이블
id | 이름 |
1 | 박재성 |
2 | 김지후 |
likes 테이블
id | 사용자 id (FK) | 게시글 id (FK) |
1 | 1 | 1 |
2 | 2 | 1 |
- 데이터 중복이 발생하게 되면 특정 데이터를 수정하려고 할 때, 그 데이터와 중복되는 모든 데이터를 같이 수정해야 한다.
- 이 관점에서 위 테이블 구성을 다시 보자. 2번 사용자가 1번 게시글의 좋아요를 삭제했다고 해보자. 그러면 likes 테이블에서 데이터만 삭제하면 될 줄 알았는데, 테이블 구성을 다시 보니 post 테이블의 좋아요 수의 값도 수정해주어야 한다.
- 결국 특정 데이터를 수정할 때, 다른 데이터도 같이 수정해주어야 하는 상황이다. 데이터 중복이 발생했을 때와 동일한 단점이 발생한다.
- 따라서 이런 경우를 보고 숨어있는 중복이라고 얘기한다. 숨어있는 중복은 주로 집계(합계, 평균, 최대값 등)의 값에서 많이 나타난다.
숨은 데이터 중복이 발생하지 않게 만드려면 posts 테이블에서 좋아요 수 컬럼을 없애야 한다. 그러면 누군가는 ‘좋아요 수는 어떻게 구하나요?’를 물어볼 수도 있을 것이다. 그건 likes 테이블을 활용해서 좋아요 수를 카운팅하면 된다.
posts 테이블
id | 제목 | 내용 | 사용자 id (FK) | |
1 | 제목1 | 내용1 | 1 |
역정규화
"좋아요 수를 매번 계산하는 건 비싸다."
매번 JOIN + COUNT 쿼리를 쓰는 것은 서버 측에서 부담이 될 수 있기 때문에 posts 테이블에 좋아요 수 컬럼을 추가하고, likes 테이블이 변경될 때마다 그 값을 함께 업데이트를 할 수도 있다.
즉, 정규화 원칙을 일부 깨뜨린 설계, 즉 역정규화된 구조를 사용하는 경우도 있다.