들어가며
이번에 새로 팀 프로젝트를 시작하며 데이터베이스 모델링을 맡게 되었다. 개념적 데이터 모델링을 통해 서비스에 필요한 엔티티 도출부터 시작해서 어느 정도 데이터 모델링을 끝마쳤다. 내가 한 데이터 모델링에 대한 확신이 없어 좀 더 좋은 데이터베이스 설계에 대한 고민이 들기 시작했다. 자료를 찾아보던 중 정규화 작업을 통해 데이터 모델링이 단계적으로 잘 이루어졌는지 점검할 수 있다는 것을 알게 되었다.
정규화라는 단어에 대해서만 들어보았지 실제로 어떤 과정을 거쳐 정규화 작업이 이루어지는지 몰라 이번 기회를 통해 공부하게 되었고, 공부한 내용을 잊지 않고자 이렇게 글로 남긴다.
이 글은 여러 블로그 자료를 공부하며 나름대로 이해한 바를 바탕으로 정리한 내용이니 내용 상에 오류가 발견될 수 있다. 치명적인 오류가 발견된다면 댓글로 남겨주시기 바란다.
정규화란?
정규화란 데이터베이스의 중복을 최소화하고 여러가지 이상들(삽입, 삭제, 갱신 이상 등)을 제거하는 작업을 말한다.
정규화는 데이터의 중복으로 인한 문제를 해결하기 위해서 속성들 간에 종속성을 분석해서 기본적으로 하나의 테이블에 표현하도록 분해하는 것이다. 하나의 테이블로 무작정 분해하는 것이 아닌 함수적 종속성이란 개념으로 분해하는 것이다.
즉, 함수적 종속성을 파악한 다음 그 함수적 종속을 기본으로 해서 속성들을 하나의 테이블로 그룹 짓는 것이다.
정규화가 필요한 이유?
데이터베이스에 데이터가 쌓이면 쌓일수록 속도가 점점 느려지며, 데이터베이스 서버를 빌리는 비용은 엄청 비싸다.
데이터베이스 속도를 빠르게 하고 비용을 절감하기 위해서는 반드시 정규화가 필요하다.
중복을 없애 데이터를 최대한 압축해야 할 필요가 있다.
정규화의 종류
정규화는 1차 정규화, 2차 정규화, 3차 정규화, 보이스/코드 정규화, 4차 정규화, 5차 정규화, 도메인/키 정규화가 있다.
이 순서가 높아지는 단계의 정규화일수록 무결성은 강화되나 과도하게 테이블이 쪼개지므로 쓸 데 없는 부하가 걸릴 수 있다.
그러므로 현실을 감안해서 어느 정도 수준의 정규화까지 행해야 하는가를 결정해야 한다. 기본으로 3차 정규화까지는 해야 하며, 3차 정규화 과정을 마치고도 사용자의 요구사항에 의해서 여러 가지 이상들이 발생할 요지가 있다면 더 높은 차원의 정규화를 해야 한다.
1차 정규화(1NF)
1차 정규화에서는 테이블에서 하나의 컬럼 당 반드시 하나의 값이 들어가도록 만들어야 한다.
1차 정규화 예시)
정규화 전 테이블은 다음과 같다.
주문번호(orderId) | 이름(name) | 나이(age) | 주문상품(orderProduct) | 생성일(createdDate) |
P00001 | 둘리 | 16 | 레고 1개, 인형 3개 | 1/16 |
P00002 | 도라에몽 | 17 | 미니카 4개, 과학상자 1개 | 7/5 |
P00003 | 홍길동 | 13 | 비행기 2개, 미니카 3개 | 3/21 |
P00004 | 김철수 | 12 | 과학상자 2개, 미니카 2개 | 7/9 |
위 테이블을 보면 주문상품에 여러 개의 상품이 들어가 있음을 알 수 있다. 일단 이것부터 쪼개는 작업이 필요하다.
주문번호(orderId) | 이름(name) | 나이(age) | 주문상품(orderProduct) | 생성일(createdDate) |
P00001 | 둘리 | 16 | 레고 1개 | 1/16 |
P00001 | 둘리 | 16 | 인형 3개 | 1/16 |
P00002 | 도라에몽 | 17 | 미니카 4개 | 7/5 |
P00002 | 도라에몽 | 17 | 과학상자 1개 | 7/5 |
P00003 | 홍길동 | 13 | 비행기 2개 | 3/21 |
P00003 | 홍길동 | 13 | 미니카 3개 | 3/21 |
P00004 | 김철수 | 12 | 과학상자 2개 | 7/9 |
P00004 | 김철수 | 12 | 미니카 2개 | 7/9 |
위 테이블을 보면 주문상품에 상품명과 상품수량이 같이 들어가 있음을 알 수 있다. 이것 또한 쪼개는 작업이 필요하다.
주문번호(orderId) | 이름(name) | 나이(age) | 상품명(productName) | 상품 수량(amount) | 생성일(createdDate) |
P00001 | 둘리 | 16 | 레고 | 1 | 1/16 |
P00001 | 둘리 | 16 | 인형 | 3 | 1/16 |
P00002 | 도라에몽 | 17 | 미니카 | 4 | 7/5 |
P00002 | 도라에몽 | 17 | 과학상자 | 1 | 7/5 |
P00003 | 홍길동 | 13 | 비행기 | 2 | 3/21 |
P00003 | 홍길동 | 13 | 미니카 | 3 | 3/21 |
P00004 | 김철수 | 12 | 과학상자 | 2 | 7/9 |
P00004 | 김철수 | 12 | 미니카 | 2 | 7/9 |
지금까지 하나의 컬럼에 하나의 값만 존재하도록 제1 정규화를 적용했다.
1차 정규화된 테이블에서는 삽입, 삭제, 갱신 이상이 일어날 수 있다.
- 삽입 이상 :
-> 부르마블 상품이 등록되지 않는 이상 상품명에 부르마블이 삽입될 수 없다
-> 이순신 회원이 가입하지 않는 이상 이름에 이순신이 삽입될 수 없다.
- 삭제 이상 :
-> 김철수 회원이 주문을 전부 취소한다면 김철수가 우리 사이트의 회원이라는 사실까지 삭제된다.
-> 둘리 회원이 인형 주문을 취소한다면 인형 제품이 우리 사이트에서 판매 중이라는 사실까지 삭제된다.
- 갱신 이상 : 도라메몽 회원의 나이를 19살로 변경한다면 나이에 대한 2개의 튜플들을 모두 갱신해줘야 한다.
1차 정규화된 테이블에서 삽입, 삭제, 갱신 이상이 일어나는 원인은 바로 기본키가 아닌 각각의 속성들(이름, 나이, 상품명 등)이 기본키에 종속적이지 않고 부분적으로 함수 종속이 되기 때문이다. 즉, 기본키를 제외한 모든 속성이 기본키에 함수 종속이 아니기 때문이다.
이러한 문제를 해결하기 위해서는 기본키에 함수 종속을 시킨 것 끼리 따로 테이블을 만들어야 하다.
1차 정규화된 테이블에서 일어난 삽입, 삭제, 갱신 이상을 2차 정규화를 적용해 없애려고 한다.
2차 정규화(2NF)
2차 정규화는 제1 정규화에서 중복되는 값이 존재한다면, 그것을 분리하여 참조하는 것이다. (관계 형성)
2차 정규화를 하기 전 PK, FK 라는 명칭에 대해 알아야 한다.
PK 는 해당 테이블임을 증명하는 고유한 키이다.
FK 는 해당 테이블의 정보가 담겨 있음을 증명할 수 있는 참조 키이다.
이제부터 앞서 다뤘던 테이블에 2차 정규화 방식을 적용하도록 하겠다.
우선 상품명이 중복되는 경우가 많아 주문 테이블로부터 상품명을 상품 테이블로 분리했다.
상품 테이블
상품 ID | 상품명 |
A0001 | 레고 |
A0002 | 인형 |
A0003 | 미니카 |
A0004 | 과학상자 |
A0005 | 비행기 |
다음으로 이름, 나이, 생성일이 중복되는 경우가 많아 주문 테이블로부터 이름, 나이, 생성일을 주문 회원 테이블로 분리했다.
주문 회원 테이블
주문 ID | 생성일 | 이름 | 나이 |
P00001 | 1/16 | 둘리 | 16 |
P00002 | 7/5 | 도라에몽 | 17 |
P00003 | 3/21 | 홍길동 | 13 |
P00004 | 7/9 | 김철수 | 12 |
기존에 존재했던 주문 테이블에서 상품명이 상품 테이블로 이름, 나이, 생성일이 주문 회원 테이블로 분리되었다.
분리되고 남은 테이블은 상품 수량 정보만이 남아 있는데, 주문한 상품 정보를 알 수 있도록 상품 ID 를 FK 로 가지고 있도록 했다.
주문 상품 테이블
주문 ID | 상품 ID | 상품 수량 |
P00001 | A0001 | 1 |
P00001 | A0002 | 3 |
P00002 | A0003 | 4 |
P00002 | A0004 | 1 |
P00003 | A0005 | 2 |
P00003 | A0003 | 3 |
P00004 | A0004 | 2 |
P00004 | A0003 | 2 |
별 문제 없이 깔끔하게 테이블이 분리된 것 같지만, 여기서도 문제점을 발견할 수 있다.
그 문제점은 바로 기본키가 아닌 속성들끼리 종속성을 가지는 이행적 함수 종속성이며, A -> B, B -> C 이면 A -> C 관계가 만들어진다.
위 예시에서는 주문 회원 테이블의 주문 ID -> 주문 상품 테이블의 주문 ID, 주문 상품 테이블의 주문 ID -> 주문 상품 테이블의 상품 ID 이면 주문 회원 테이블의 주문 ID -> 주문 상품 테이블의 상품 ID 의 관계가 만들어진다.
이행적 함수 종속성이 원인이 되어 삽입, 삭제 이상이 일어날 수 있다.
- 삽입 이상 : 이순신 회원이 레고 2개를 주문한다는 사실을 삽입하려 할 때 이순신 회원이 존재하지 않는다면 이 사실을 삽입할 수 없다. 즉, 주문 회원 테이블에 이순신 회원에 대한 주문 ID(기본키) 가 존재하지 않아 주문 상품 테이블에 레고 2개 주문에 대한 데이터 삽입이 불가능하다는 것이다. 주문 ID 는 주문 상품 테이블에서 반드시 들어가야만 하는 것인데 이를 무시한 채 다른 속성값을 삽입하려 한 것이 문제이다.
- 삭제 이상 : 만약 둘리 회원이 우리 사이트를 더 이상 이용하고 싶지 않아 탈퇴한다면 둘리가 레고 1개, 인형 3개를 주문했었다는 사실이 없어진다. 둘리가 레고 1개, 인형 3개를 주문했었다는 사실은 이행 종속이 일어나고 있다는 것이며, 역시 2차 정규화된 테이블에서 일어나는 삭제 이상도 이행 종속 때문이라는 것을 알 수 있다.
2차 정규화된 테이블에서 이행 종속에 의해서 여러 가지 이상 현상(삽입, 삭제 이상)이 발생되는 것을 보았다. 이러한 여러 이상 현상을 제거하려면 이상 현상의 원인이 되었던 이행 종속을 없애면 된다.
이행 종속을 3차 정규화를 적용해 없애려고 한다.
3차 정규화(3NF)
3차 정규화는 2차 정규화를 적용한 후 이행 종속을 제거한 정규형이다.
앞서 다뤘던 테이블에 3차 정규화 방식을 적용하도록 하겠다.
상품 테이블
상품 ID(PK) | 상품명 |
A0001 | 레고 |
A0002 | 인형 |
A0003 | 미니카 |
A0004 | 과학상자 |
A0005 | 비행기 |
회원 테이블
회원 ID(PK) | 이름 | 나이 |
A00001 | 둘리 | 16 |
A00002 | 도라에몽 | 17 |
A00003 | 홍길동 | 13 |
A00004 | 김철수 | 12 |
주문 회원 테이블
생성 ID(PK) | 회원 ID(PK) | 생성일 |
P00001 | A00001 | 1/16 |
P00002 | A00002 | 7/5 |
P00003 | A00003 | 3/21 |
P00004 | A00004 | 7/9 |
주문 상품 테이블
주문 ID(PK) | 상품 ID(PK) | 상품 수량 |
P00001 | A0001 | 1 |
P00001 | A0002 | 3 |
P00002 | A0003 | 4 |
P00002 | A0004 | 1 |
P00003 | A0005 | 2 |
P00003 | A0003 | 3 |
P00004 | A0004 | 2 |
P00004 | A0003 | 2 |
이 테이블들을 기준으로 어떤 흐름으로 진행되는지 확인해보겠다.
1. 상품은 관리자에 의해 삽입되거나 삭제된다.
2. 사용자는 회원가입을 해야 상품을 주문할 수 있다.
3. 어떤 회원이 상품을 주문할 경우 이력이 남는다.
4. 해당 이력에는 어떤 상품을 몇 개 구매했는지 확인할 수 있다.
이렇게 되면 사용자가 없더라도 상품을 추가할 수 있으며, 사용자는 상품이 없더라도 회원가입을 할 수 있다.
마치며
이번 글을 통해 1차 정규화부터 3차 정규화까지 단계별로 정규화함에 따라 어떻게 데이터 모델링이 이루어지는지 이해할 수 있는 시간이었다. 학습한 바를 바탕으로 팀 프로젝트에 정규화를 적용해볼 것이다.
정규화를 공부하며 함수 종속성에 대한 개념이 자주 등장했는데, 아직 함수 종속성에 대한 이해가 부족해 추가적으로 공부하는 시간을 가져야 할 것 같다. 추후 함수 종속성에 대해 공부한 뒤 글로 정리할 것이다.