티스토리 뷰
DB에 설정을들여다보다가 뜬금없지만, 정규화(Normalization)에 대해 정리를 하고 넘어가는게 좋겠다 생각이 들었다.
정규화의 목적은 DB CRUD 중 이상현상(Abnomaly) 발생과 컬럼 간 종속성을 피하기 위함이다.
- 삭제 이상: 데이터를 삭제하면, 의도하지 않은 다른 정보까지 삭제되어버리는 현상
- 삽입 이상: 데이터를 삽입할때, 특정 컬럼에 해당하는 값이 없어 NULL을 입력해야 하는 현상
- 갱신 이상: 데이터를 갱신할때, 중복된 데이터 중 일부만 수정되어 데이터 불일치 현상
이러한 이상 현상들을 피하기 위해 정규화를 거친다. 정규화를 통해 데이터 중복을 피하고, 무결성을 유지하는게 목표이다.
코딩애플 유튜브를 보면서 정리했다.
1. 제 1 정규화
제 1 정규화는 테이블의 컬럼이 원자값(Atomic Value)을 갖도록 컬럼 값을 분해하는 것이다.
위와 같은 테이블이 있다 가정했을 때, 드리블 강의를 조회하려고 한다면 아래와 같이 LIKE 연산자를 써야한다.
SELECT * FROM SOCCER WHERE 강의 LIKE '%드리블%'
이건 너무 귀찮고, 강의 내용 수정도 어렵다. 당장 저 테이블의 드리블 강의를 수정하라면, 한번에 업데이트 칠 방법도 떠오르지 않는다.
그래서 아래와 같이 테이블을 변경한다.
그럼 조회도 LIKE 문을 사용하지 않고, 편안하게 할 수 있다.
SELECT * FROM SOCCER WHERE 강의 ='드리블'
한 컬럼엔 한데이터만!! → 제 1 정규형 테이블이다.
2. 제 2 정규화
제 2 정규화는 제 1 정규화를 만족하는 테이블이 완전 함수 종속을 만족하도록 분해하는 것이다.
- 완전 함수 종속 : 컬럼 값이 기본 키(한 릴레이션의 다른 속성들을 모두 결정하는 값)에 대해 완전히 종속되는 경우를 지칭
우선 위 테이블에서 드리블의 강의료를 변경하고 싶다. 그러면, 두 번의 수정이 필요하다. 위 테이블에서야 두 개지만 데이터가 수천 수만가지가 된다면, 수천 수만번 업데이트를 해줘야 한다는 뜻이다.
너무 불편하다.
정규형을 하기 위해서 위 테이블을 뜯어 볼 필요가 있는데, 위 테이블에서 강의와 학생번호가 결합한 (학생번호, 강의)는 전체 릴레이션 값을 결정 짓는 기본키가 될 수 있다. 이 상황에서 기본키의 일부인 강의는 강의료를 결정된다. 이럴 경우 부분 함수 종속에 해당한다.
- 부분 함수 종속: 컬럼 값이 기본키가 아닌 다른 속성에 종속되거나, 기본키가 여러 속성으로 구성되어 있을경우 기본키를 구성하는 속성 중 일부만 종속될 때
때문에 위 테이블은 완전 함수 종속을 만족하지 않는다. 때문에 제 2 정규형을 만족하는 테이블이라고 볼 수 없다.
제 2 정규화 테이블이 될 수 있도록 위 테이블을 분리해주자.
위와 같이 분리해주면 부분 함수 종속이 사라졌기 때문에, 제 2 정규형을 만족하는 테이블이 된다. 이렇게 되면 오른쪽 테이블의 강의료를 하나만 수정해도 왼쪽 테이블에도 반영이 된다.
하지만 이렇게 변경하게되면 손흥민이 얼마를 내야하냐?를 한눈에 볼 수 없다는 단점이 생긴다. 조회 쿼리를 만들게 된다면 join이 필요해진다는 의미다.
3. 제 3 정규화
테이블이 제 2 정규형에 속하고, 기본키가 아닌 모든 속성이 기본키에 이행적 함수 종속이 되지 않으면 제 3 정규형이다.
- 이행적 함수 종속 : A를 알면 B를 알고 그를 통해 C를 알 수 있는 경우를 의미
위 테이블에서 학생번호 -> 이름 -> 소속팀이 성립해, 학생번호 -> 소속팀이 성립한다. 위와 같은 조건일 때, 이행적 함수 종속 상태라고 하며 제 3 정규형을 만족하지 않는다. 이럴 때는 또 분리...한다.
이행적 함수 종속도 제거했고, 완전 함수 종속을 만족하기 때문에 제 3 정규형을 만족하는 테이블들이 됐다.
4. BCNF 정규화
대부분의 테이블에서는 BCNF(Boyce-Codd Normal Form) 정규화 까지 진행하면 실질적인 이상현상이 사라진다고 한다.
강한 제 3 정규화라고도 하는 것 같은데, 실제로는 잘 발생하지 않는 특이 케이스 같다. 때문에 별도의 예시를 다루진 않고 링크로 남긴다.
이후에 고급 정규화? 라는 4,5 정규화들이 있지만 일반적으로 잘 사용하지 않는다.
5. 마치며
실무에서 사용되는 데이터와 시나리오는 단순하지 않다. 때문에 3단계 적용도 쉽지가 않다.
정규화 과정을 거침에 따라 과도한 join 사용을 하게 됐고, Full scan을 피하기 위해 무분별한 index 설정을 하게 됐다. 더 문제는 중간 중간 들어오는 추가 요구 사항들과 개발 기한의 압박으로 인해, 초기 설계는 결국 엉망이 됐었다.
정규화도 중요하지만 확장성 있게끔 컬럼을 분류하고, 목적에 맞게끔 테이블을 나눌 수 있는 능력을 키워야 겠다는 생각을 했었다. 언젠가는 다 잘할 수 있게 되지 않을까?
'개발 > DB' 카테고리의 다른 글
스프링부트에 QueryDSL 적용기 - 1 (Mybatis vs JPA vs JOOQ vs QueryDSL 비교) (0) | 2023.08.25 |
---|---|
The MySQL server is running with the --read-only option so it cannot execute this statement 에러와 @Transactional (1) | 2023.06.01 |
[DB] 파티셔닝(Partitioning), 샤딩(Sharding) (0) | 2023.03.12 |
SQL Mapper, MyBatis (0) | 2023.01.01 |
[DB] INDEX (1) | 2022.12.27 |
- Total
- Today
- Yesterday
- GIT
- openAI API
- MySQL
- Spring
- Log
- springboot
- 티스토리챌린지
- cache
- 스프링부트
- ChatGPT
- 오블완
- serverless
- JWT
- lambda
- elasticsearch
- 람다
- 후쿠오카
- Elastic cloud
- S3
- AOP
- AWS
- CloudFront
- OpenAI
- OpenFeign
- terraform
- AWS EC2
- Kotlin
- java
- docker
- EKS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |