정규화와 반정규화
정규화
데이터 중복을 최소화하고, 데이터 무결성을 유지하기 위해 데이터베이스를 설계하는 과정
💡데이터 무결성 (Data Integrity)
데이터가 정확하고 일관되며 신뢰할 수 있는 상태로 유지되는 것
💡데이터 무결성의 유형
개체 무결성 (Entity Integrity)
기본 키(Primary Key)는 반드시 NULL이 아니고 중복되지 않아야 함.
참조 무결성 (Referential Integrity)
외래 키(Foreign Key)는 참조하는 테이블의 기본 키와 일치하거나 NULL이어야 함.
도메인 무결성 (Domain Integrity)
컬럼 값은 정해진 데이터 타입, 범위, 형식을 만족해야 함.
사용자 정의 무결성 (User-defined Integrity)
비즈니스 규칙에 따른 무결성.
제 1 정규화 (1NF)
테이블의 모든 컬럼이 원자값을 가지도록 하여 데이터의 중복을 줄이는 것

제 2 정규화 (2NF)
제 1 정규형을 만족하면서 부분 함수 종속을 제거한 것
(현재 테이블의 주제와 관련 없는 컬럼을 다른 테이블로 분리)
💡부분 함수 종속
기본키 중에 특정 컬럼에만 종속되는 것

⬇️⬇️⬇️

제 3 정규화 (3NF)
제 2 정규형을 만족하면서 이행 함수 종속을 제거한 것
💡이행 함수 종속
A → B, B → C 일 때 A → C 를 만족하면 이행 함수 종속이라고 한다.

⬇️⬇️⬇️

반정규화
성능 향상을 위해 일부러 중복을 허용하고 테이블을 병합하는 설계 방식
- 조회 속도가 중요한 테이블
- 실시간 집계가 필요한 경우
대표 예시: https://bezzang2.tistory.com/145
실제 적용 예
[table-now] 예약자-사장님 1:1 실시간 채팅 기능 설계 및 구현
🔍 구현 목적`table-now`는 실시간 식당 예약 서비스입니다.예약자와 가게 사장님 간의 실시간 소통을 지원하기 위해 1:1 채팅 기능을 구현했습니다. 단순한 메시지 전송이 아닌, 예약 건 단위로 안
mannakingdom.tistory.com
인덱스
추가적인 쓰기 작업과 저장 공간을 활용하여 데이터베이스 테이블의 검색 속도를 향상시키기 위한 자료구조

- 정렬된 데이터 구조(주로 B-Tree 또는 Hash)로 구성
- 데이터를 전체 탐색하지 않고, 필요한 위치로 바로 접근 가능
- 자주 조회되는 컬럼에 인덱스를 만들면 성능이 향상
인덱스의 장단점
장점
- 특정 컬럼을 기준으로 데이터를 빠르게 조회 가능
- 테이블의 전체 스캔을 피하고, 필요한 데이터만 빠르게 탐색
- `ORDER BY`, `GROUP BY` 구문에서 인덱스가 있으면 정렬 작업 최소화
- `UNIQUE INDEX`를 통해 중복 데이터 입력을 사전에 차단 가능
- FK나 JOIN 조건 컬럼에 인덱스가 있으면 빠른 조인 처리 가능
단점
- `INSERT`, `UPDATE`, `DELETE` 시 인덱스도 함께 갱신되므로 오버헤드 발생
- 인덱스 자체도 저장공간을 차지하며, 복합 인덱스일수록 크기 증가
- 너무 많은 인덱스를 걸면 오히러 쓰기/갱신 성능이 떨어지고, 옵티마이저가 잘못된 인덱스를 선택할 수도 있음
- 인덱스를 이용해도 `ORDER BY` 조건과 맞지 않으면 성능 개선이 없음
- 함수 사용, `LIKE`, `OR`, `NOT` 등 조건은 인덱스를 제대로 못 쓸 수도 있음
=>
읽기 위주의 테이블에 인덱스 활용
쓰기 작업이 많은 테이블은 인덱스 최소화
필요한 컬럼만 선별적으로 인덱싱
인덱스 구조
데이터를 어떤 자료구조로 저장하고 탐색할지 결정
B-Tree 인덱스

특징
- 내부적으로 데이터가 오름차순 또는 내림차순으로 정렬됨
- 범위 조건 처리에 유리 (예: `WHERE age BETWEEN 20 AND 30`)
- 트리의 깊이가 균형있게 유지되어 O(log n) 탐색 보장
B+Tree

- 리프 노드에만 실제 데이터 위치 저장 (데이터 접근 방식이 균일)
- 리프 노드끼리 연결되어 있음 (범위 검색 최적화)
- 대부분의 RDBMS는 B+Tree 구조로 인덱스를 구현함
Hash 인덱스
해시 함수를 사용하여 인덱스 키 -> 고유한 버킷 주소로 매핑

특징
- 값이 정확히 일치하는 조건에 대해 매우 빠름 (예: `WHERE id = 10`)
- `BETWEEN`, `>`, `LIKE` 등은 사용 불가
- 서로 다른 값이 같은 해시 버킷으로 매핑될 수 있음
- 데이터가 정렬되지 않기 때문에 `ORDER BY` 사용할 수 없음
- MySQL의 Memory 엔진에서만 `USING HASH`로 직접 생성 가능
- 실무에서는 거의 사용되지 않음
- 대부분의 RDBMS는 Hash Index를 직접 지원하지 않거나 내부 용도로만 사용
- 일반적인 쿼리 최적화를 위해서는 B+Tree 인덱스를 기본으로 고려하는 것이 좋다.
트랜잭션
데이터베이스에서 하나의 논리적인 작업 단위
여러 개의 작업이 모두 성공하거나, 모두 실패해야 하는 실행 단위
트랜잭션의 특징 (ACID)
| A - 원자성 (Atomicity) | 전부 성공하거나, 전부 실패해야 함 All or Nothing |
| C - 일관성 (Consistency) | 트랜잭션 전후로 DB는 항상 일관된 상태를 유지해야 함 |
| I - 격리성 (Isolation) | 동시에 실행되는 트랜잭션끼리는 서로 간섭하면 안 됨 |
| D - 지속성 (Durability) | 트랜잭션이 커밋되면 결과는 영구 반영되어야 함 |
Spring에서 트랜잭션 관리
- 어노테이션으로 관리: `@Transactional`
- 주로 Service 계층에 붙임
- 예외 발생 시 자동으로 rollback
예: 회원가입 트랜잭션
@Transactional
public SignupResponse signup(SignupRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new HandledException(ErrorCode.DUPLICATE_EMAIL);
}
String encodedPassword = passwordEncoder.encode(request.getPassword());
User newUser = User.builder()
.email(request.getEmail())
.password(encodedPassword)
.name(request.getName())
.nickname(request.getNickname())
.phoneNumber(request.getPhoneNumber())
.userRole(request.getUserRole())
.build();
User savedUser = userRepository.save(newUser);
return SignupResponse.builder()
.id(savedUser.getId())
.email(savedUser.getEmail())
.name(savedUser.getName())
.nickname(savedUser.getNickname())
.phoneNumber(savedUser.getPhoneNumber())
.userRole(savedUser.getUserRole().name())
.build();
}
회원가입 과정에서
- 중복 이메일이 있는지 확인하고,
- 패스워드를 암호화하고,
- 새 유저로 저장하고,
- 저장된 유저 정보를 반환하는 것 까지가
하나의 트랜잭션
JOIN
JOIN 종류

| INNER JOIN | 양쪽 테이블에 공통으로 존재하는 값만 조회 (교집합) |
| LEFT OUTER JOIN | 왼쪽 테이블을 기준으로 일치하지 않는 오른쪽 테이블 값은 NULL로 채움 |
| RIGHT OUTER JOIN | 오른쪽 테이블을 기준으로 왼쪽 테이블에 없는 값은 NULL로 채움 |
| FULL OUTER JOIN | 전체 합집합 조회 |
| CROSS JOIN | 두 테이블의 모든 조합 (곱집합, N x M) |
참고
데이터 정규화가 뭔지 설명해보세요 (개발면접타임) - YouTube
https://f-lab.kr/insight/understanding-database-normalization-and-denormalization
https://velog.io/@wisdom-one/%EC%A0%95%EA%B7%9C%ED%99%94Normalization
https://mangkyu.tistory.com/96
index가 뭔지 설명해보세요 (개발면접시간) - YouTube
https://velog.io/@calis_ws/DB-Hash-index-%EC%99%80-B-Tree-index
'TIL (Today I Learned)' 카테고리의 다른 글
| Spring Boot 면접 대비 개념 정리❕ (1) | 2025.06.19 |
|---|---|
| JVM의 개념과 내부 구조 정리 (4) | 2025.06.19 |
| Synchronous 🆚 Asynchronous | RabbitMQ 기반 비동기 처리 구조 (2) | 2025.06.05 |
| JWT 인증 기반 WebSocket 연결 실패, 원인과 해결법 (2) | 2025.06.05 |
| Spring Boot 설정값 바인딩 정리: @Value vs @ConfigurationProperties (2) | 2025.05.24 |