13주차 자료의 모든 토픽을 "DB 이론 → SQL 성능"의 두 축으로 재배열한 학습 경로.
12주차까지 Java/Spring/JPA를 다뤘다면, 13주차는 데이터베이스의 펀더멘털로 방향 전환.1) Part A — DB 이론과 관리 (모델링, 정규화, NoSQL, 분산 시스템 이론, 락)
2) Part B — SQL 성능 최적화 (옵티마이저, 인덱스, B-tree, EXPLAIN)JPA가 추상화해서 가려놓았던 DB 본연의 영역을 정면으로 보는 주차다.
[Part A — DB 이론과 관리]
[Phase 1] DB 핵심 용어와 모델링 (엔티티/릴레이션/스키마)
↓
[Phase 2] 정규화 (1NF → 2NF → 3NF → BCNF)
↓
[Phase 3] 이상 현상과 정규화 평가
↓
[Phase 4] SQL Injection 보안
↓
[Phase 5] RDBMS와 NoSQL (Scale-Up vs Scale-Out)
↓
[Phase 6] 분산 시스템 이론 + 락 (BASE/CAP/PACELC + S/X Lock)
[Part B — SQL 성능 최적화]
[Phase 7] 옵티마이저 (RBO/CBO + 동작 6단계)
↓
[Phase 8] 인덱스 기초 (빅오, B-tree) ◄ 정점 1
↓
[Phase 9] 멀티컬럼 인덱스와 EXPLAIN
↓
[Phase 10] 인덱스 최적화 전략 (Covering Index, Full Scan 선택)
총 10 Phase × 31 Unit — 단일 주차로는 큰 분량 (Part A/B 구조).
| 주차 | 주제 | 핵심 변화 |
|---|---|---|
| 1주차 | OOP·JVM·GC·컬렉션·I/O 개론 | 자바 큰 그림 |
| 2주차 | JVM 내부·바이트코드·G1 GC | "어떻게 돌아가나" |
| 3주차 | 컬렉션·제네릭·함수형 | 자바 표현력 |
| 4주차 | 멀티스레딩·동시성·Executor | 동시성 정복 |
| 5주차 | Atomic + Spring IoC/DI 입문 | 자바 → Spring 다리 |
| 6주차 | 테스트 + 웹 인프라 + DB 접근 진화 | Spring 실전 환경 |
| 7주차 | JPA/ORM 입문 + 트랜잭션 추상화 | DB 추상화 입문 |
| 8주차 | 프록시의 진화 | AOP 메커니즘 |
| 9주차 | Spring AOP 실전 + 트랜잭션 전파 | AOP 실전 활용 |
| 10주차 | 트랜잭션 정리 + 빈 라이프사이클 함정 + 격리 수준 | 트랜잭션 마무리 |
| 11주차 | JPA의 정체와 영속성 컨텍스트 | JPA 메커니즘 완전 이해 |
| 12주차 | 연관관계 + N+1 등 성능 최적화 | JPA 실전 활용 |
| 13주차 (지금) | DB 펀더멘털 - 모델링부터 인덱스까지 | DB 본연의 영역으로 |
| Day | Phase | 학습 목표 |
|---|---|---|
| Part A | ||
| 1일차 | Phase 1 + 2 | DB 용어 + 정규화 (1NF~BCNF) |
| 2일차 | Phase 3 + 4 | 이상 현상 + SQL Injection |
| 3일차 | Phase 5 | RDBMS와 NoSQL (Scale, Redis) |
| 4일차 | Phase 6 | BASE/CAP/PACELC + 공유락/베타락 |
| Part B | ||
| 5일차 | Phase 7 + 8 | 옵티마이저 + 인덱스 기초 (B-tree) ★ |
| 6일차 | Phase 9 | 멀티컬럼 인덱스 + EXPLAIN |
| 7일차 | Phase 10 + 종합 | Covering Index + 인덱스 최적화 + 자기 점검 |
여유 일정 (10일): Phase 8과 Phase 9에 각 +1일. 인덱스는 직접 SQL을 돌리며 EXPLAIN으로 확인해야 체화됨.
목표: JPA가 가려놓았던 DB의 기본 어휘를 정확히 잡는다.
선수 지식: 6주차 Phase 6 (DB 기초), 11주차 Phase 3
핵심 개념
엔티티(Entity):
릴레이션(Relation):
비유:
"엔티티는 클래스, 릴레이션은 인스턴스의 모임(테이블)"
ILIC 사례:
Booking 객체 (개념)bookings 테이블 (물리)@Entity 가 이 두 개념을 잇는 다리자기 점검
@Entity 어노테이션이 매핑하는 두 세계는?선수 지식: Unit 1.1
핵심 5가지 용어 ⭐ :
| 용어 | 의미 | 예시 (고객 테이블) |
|---|---|---|
| 어트리뷰트(Attribute) | 속성 = 열(Column) | 이름, 전화번호, 주소 |
| 차수(Degree) | 어트리뷰트의 개수 | 4 |
| 튜플(Tuple) = 레코드 | 행(Row) 하나 | 김철수, 010-..., 서울 |
| 카디널리티(Cardinality) | 튜플의 개수 | 1000 |
| 도메인(Domain) | 어트리뷰트가 가질 수 있는 값의 집합 | 안경 착용 = {유, 무} |
핵심 차이:
도메인 예시:
CREATE TABLE customer (
glasses VARCHAR(2) CHECK (glasses IN ('유', '무')) -- 도메인 = {유, 무}
);
→ JPA로 표현하면 enum 또는 @Check 어노테이션
자기 점검
bookings 테이블의 차수와 카디널리티는?선수 지식: Unit 1.2
핵심 개념
스키마:
"데이터베이스의 데이터 구조 정의"
3계층 구조 ⭐ :
| 계층 | 정의 | 표현 방식 |
|---|---|---|
| 개념적 스키마 | 모든 사용자 공유 구조 | ERD (Entity-Relationship Diagram) |
| 논리적 스키마 | 특정 DB 모델로 변환 | SQL DDL 구문 |
| 물리적 스키마 | 디스크 저장 방식 | 인덱스, 파티션, 클러스터링 |
예시 흐름:
[설계] 고객 ↔ 주문 (1:N) — ERD
↓
[변환] CREATE TABLE customer (...); -- DDL
CREATE TABLE order (...);
↓
[저장] B-tree 인덱스, 페이지 크기 16KB, ...
스키마 vs 데이터베이스의 관계:
자기 점검
목표: 데이터 중복을 줄이고 이상 현상을 제거하는 단계적 분해 기법을 마스터한다. 면접 단골.
선수 지식: Phase 1
핵심 개념
정규화(Normalization):
"속성 간 종속 관계를 분석하여 여러 릴레이션으로 분해 하는 과정"
목적:
단계적 분해 ⭐ :
원본 → 1NF → 2NF → 3NF → BCNF → ... (4NF, 5NF는 거의 안 씀)
실무 권장:
자기 점검
선수 지식: Unit 2.1
핵심 규칙:
"테이블의 모든 컬럼이 원자값(Atomic Value) 을 가져야 한다"
위반 사례:
| 이름 | 취미 |
|---|---|
| 김연아 | 인터넷 |
| 추신수 | 영화, 음악 ← 원자값 X! |
| 박세리 | 음악, 쇼핑 |
1NF 적용 후:
| 이름 | 취미 |
|---|---|
| 김연아 | 인터넷 |
| 추신수 | 영화 |
| 추신수 | 음악 |
| 박세리 | 음악 |
| 박세리 | 쇼핑 |
모범 사례:
현대 DB의 예외 (참고):
ARRAY 타입JSON 컬럼자기 점검
선수 지식: Unit 2.2
핵심 규칙:
"1NF + 기본 키의 일부에만 종속된 속성을 다른 테이블로 분리"
전제: 기본 키가 복합 키(composite key)일 때 의미가 있음
위반 사례:
기본 키 = (학생ID, 강의명)
| 학생ID | 강의명 | 학생명 | 교수명 |
|---|---|---|---|
| 101 | 데이터베이스 | Alice | 김교수 |
| 101 | 운영체제 | Alice | 박교수 |
종속 분석:
학생명은 학생ID 만으로 결정 ← 부분 함수 종속교수명은 강의명 만으로 결정 ← 부분 함수 종속2NF 적용 — 3개 테이블로 분리:
[학생 테이블] (학생ID 기본 키)
| 학생ID | 학생명 |
|---|---|
| 101 | Alice |
[강의 테이블] (강의명 기본 키)
| 강의명 | 교수명 |
|---|---|
| 데이터베이스 | 김교수 |
[수강 테이블] (학생ID, 강의명 복합 키)
| 학생ID | 강의명 |
|---|---|
| 101 | 데이터베이스 |
| 101 | 운영체제 |
핵심 통찰:
"부분 함수 종속 = 복합 키의 일부분에만 의존하는 속성"
자기 점검
선수 지식: Unit 2.3
핵심 규칙 (3NF):
"2NF + 기본 키가 아닌 속성이 이행 함수 종속(Transitive Dependency) 되지 않음"
이행 함수 종속:
"A → B 이고 B → C 일 때 A → C 인 관계"
위반 사례 (기본 키 = 이름):
| 이름 | 학과 | 등록금 |
|---|---|---|
| 철수 | 기계공학 | 350 |
| 영희 | 수학 | 250 |
| 민수 | 화학공학 | 300 |
종속 분석:
문제:
3NF 적용:
[학생 테이블]
| 이름 | 학과 |
|---|---|
| 철수 | 기계공학 |
[학과 테이블]
| 학과 | 등록금 |
|---|---|
| 기계공학 | 350 |
BCNF (Boyce-Codd Normal Form):
"3NF + 모든 결정자가 후보 키여야 함"
용어:
위반 사례:
| 학생번호 | 과목 | 지도교수 |
|---|
후보 키: (학생번호, 과목) → 지도교수 결정 가능
하지만: 지도교수 → 과목 도 결정 가능
→ BCNF 위반
BCNF 적용:
실무 결론:
"보통 3NF까지 정규화. BCNF는 특수 상황"
자기 점검
목표: 정규화의 동기(왜 필요한가)와 한계(언제 안 좋은가)를 모두 본다.
선수 지식: Phase 2
핵심 3가지 ⭐ :
삽입 이상(Insertion Anomaly):
"특정 데이터 삽입 시 불필요한 데이터 까지 함께 입력해야 함"
예: "알고리즘" 신규 과목을 추가하려는데 학생이 없으면 NULL을 넣거나 더미 학생 정보 필요
갱신 이상(Update Anomaly):
"데이터 수정 시 여러 레코드를 모두 수정해야 함 — 일부만 수정 시 불일치"
예: 김교수의 연락처 변경 시 김교수가 가르치는 모든 강의 레코드 갱신 필요
UPDATE 강의정보 SET 교수연락처 = '...' WHERE 교수ID = 1;
-- 5개 레코드 모두 갱신 — 일부 누락 시 불일치
삭제 이상(Deletion Anomaly):
"특정 데이터 삭제 시 의도하지 않은 데이터 까지 함께 삭제됨"
예: 학생이 수강 취소 → 그 학생이 마지막 수강자였다면 → 과목 정보까지 사라짐
3가지가 발생하는 근본 원인:
"서로 다른 의미의 정보가 한 테이블에 묶여 있음"
→ 정규화 가 해결 ⭐
ILIC 관점 점검:
자기 점검
선수 지식: Unit 3.1
핵심 평가
장점:
단점:
반정규화(Denormalization):
"성능을 위해 의도적으로 일부 데이터 중복 허용"
언제 반정규화?:
1. 자주 함께 조회되는 데이터 → JOIN 비용 절감
2. 통계 마트, 보고서용 테이블
3. 읽기 비율이 압도적으로 높은 테이블
ILIC 사례:
원칙:
"정규화 후 반정규화 — 절대 처음부터 비정규화로 시작하지 말 것"
자기 점검
목표: 가장 흔한 웹 보안 취약점을 이해하고 표준 방어법을 익힌다.
선수 지식: 6주차 Phase 7 (JdbcTemplate)
핵심 개념
SQL Injection:
"사용자 입력에 악의적인 SQL 코드를 주입 하여 쿼리를 조작하는 공격"
취약 코드:
String username = request.getParameter("username");
String query = "SELECT * FROM users WHERE username = '" + username + "'";
// ↑ 직접 문자열 결합 (위험!)
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);
공격 시나리오:
' OR '1'='1 입력SELECT * FROM users WHERE username = '' OR '1'='1'
-- ↑ 항상 참 → 모든 사용자 조회!
다른 공격 패턴:
'; DROP TABLE users; -- → 테이블 삭제' UNION SELECT password FROM admins -- → 권한 우회피해:
자기 점검
선수 지식: Unit 4.1
핵심 방어 — Prepared Statement ⭐ :
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username); // 입력값이 SQL이 아닌 데이터로 인식
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
왜 안전한가:
', ;, -- 등이 자동 이스케이프JPA의 자동 적용:
:userName)다층 방어 (Defense in Depth):
public static String sanitizeInput(String input) {
return input.replaceAll("[^a-zA-Z0-9]", ""); // 영숫자만
}
Spring Security의 도움:
@Valid, @Pattern)자기 점검
PreparedStatement를 쓰면 정말 100% 안전한가? (힌트: 동적 테이블명/컬럼명은 여전히 위험)목표: 데이터 저장의 두 패러다임을 비교하고, ILIC가 왜 RDBMS인지 이해한다.
선수 지식: 6주차 Phase 6, 10주차 Phase 5
핵심 정의
RDBMS (Relational Database Management System):
"데이터를 테이블 형태로 저장하고 관계를 정의하여 관리"
4가지 핵심 특징:
| 제약 | 의미 |
|---|---|
| PRIMARY KEY | 행 식별, 중복/NULL 불가 |
| FOREIGN KEY | 다른 테이블 참조 |
| UNIQUE | 중복 방지 |
| NOT NULL | 필수 입력 |
| CHECK | 값 범위/조건 검증 |
대표 RDBMS:
자기 점검
선수 지식: Unit 5.1
핵심 비교:
| Scale-Up (수직 확장) | Scale-Out (수평 확장) | |
|---|---|---|
| 방식 | 단일 서버 성능 업그레이드 | 여러 서버 추가 |
| 비유 | 더 큰 컴퓨터 | 컴퓨터 여러 대 |
| 적합 | RDBMS | NoSQL |
| 한계 | 하드웨어 한계, 비용 ↑ | 데이터 정합성, 네트워크 비용 |
| 예 | CPU/RAM/SSD 강화 | Sharding, Replication |
RDBMS가 Scale-Up 선호하는 이유:
NoSQL이 Scale-Out 선호하는 이유:
Replication (복제):
ILIC의 현실:
자기 점검
선수 지식: Unit 5.2
RDB의 한계 (NoSQL 등장 동기):
1. 유연한 확장성 부족 (스키마 변경 비용)
2. 데이터 중복 비허용 → 복잡한 JOIN
3. 단일 서버 트래픽 한계
4. ACID 보장의 성능 비용
NoSQL = "Not Only SQL":
NoSQL 5가지 특징:
1. 유연한 스키마 — 무결성 검증을 앱에서 책임
2. 높은 확장성 — Scale-Out 친화
3. 빠른 처리 속도 — JOIN 회피, 중첩 저장
4. 다양한 저장 방식 — JSON, Key-Value, Graph 등
5. BASE 모델 — ACID 대신
4가지 NoSQL 유형 ⭐ :
| 유형 | 저장 방식 | 대표 제품 | 사용 사례 |
|---|---|---|---|
| Key-Value | 키-값 단순 저장 | Redis, DynamoDB | 캐시, 세션 |
| Document | JSON/BSON 문서 | MongoDB, CouchDB | 다양한 구조 데이터 |
| Column-Family | 열 기반 저장 | Cassandra, HBase | 대량 시계열, 분석 |
| Graph | 노드 + 엣지 | Neo4j, Amazon Neptune | 관계 분석, 추천 |
Document Store 핵심 — 컬렉션(Collection):
// 같은 users 컬렉션 안에 두 다른 구조 OK
{ "name": "Alice", "age": 25 }
{ "name": "Bob", "skills": ["Java", "Python"] }
Graph DB의 강력함:
금융/결제/예약 같은 데이터 일관성 중요 영역은 NoSQL 사용 조심.
자기 점검
선수 지식: Unit 5.3, 4주차 Phase 5
핵심 개념
Redis (Remote Dictionary Server):
활용 영역 ⭐ :
[Client] → [App] → [Redis] (있으면 반환)
↓ (없으면)
[DB] → [Redis 저장] → [Client]
메시지 큐 (Pub/Sub)
분산 락:
SETNX 명령으로 단일 락 구현유튜브 캐싱 예시:
1. 사용자가 동영상 요청
2. Backend → Redis 조회 (TTL 60초 설정)
3. 있으면 → 즉시 반환 (DB 안 거침)
4. 없으면 → DB 조회 → Redis 저장 → 반환
고가용성 (High Availability):
ILIC 적용 가능:
자기 점검
목표: 데이터베이스의 일관성·가용성 트레이드오프 이론과 동시성 제어를 이해한다.
선수 지식: Phase 5, 6주차 ACID
핵심 개념
BASE = ACID의 반대 철학 ⭐ :
3가지 풀어보기:
1. Basically Available (기본적 가용성):
2. Soft State (부드러운 상태):
3. Eventually Consistent (최종적 일관성):
예시 — 분산 환경 게시물 업로드:
1. 사용자가 게시물 업로드
2. 일부 서버: 즉시 반영
3. 일부 서버: 잠시 동안 보이지 않음
4. 몇 초~몇 분 후: 모든 서버 동기화 완료
ACID vs BASE:
| 측면 | ACID | BASE |
|---|---|---|
| 일관성 | 즉시 강한 일관성 | 최종적 일관성 |
| 가용성 | 일관성 위해 양보 | 우선 보장 |
| 트랜잭션 | 강력 | 느슨 |
| 적합 영역 | 금융·결제 | SNS·로그·검색 |
자기 점검
선수 지식: Unit 6.1
핵심 이론
CAP 이론:
"분산 시스템에서 Consistency, Availability, Partition Tolerance 중 2가지만 만족 가능"
3가지 속성:
| 정의 | |
|---|---|
| C Consistency (일관성) | 모든 노드에서 같은 값 |
| A Availability (가용성) | 모든 요청에 응답 |
| P Partition Tolerance (분할 허용성) | 네트워크 단절 시에도 동작 |
3가지 조합:
CP (일관성 + 분할 허용성) — NoSQL:
AP (가용성 + 분할 허용성) — NoSQL:
CA (일관성 + 가용성) — RDBMS:
중요한 통찰:
"현실 분산 시스템에서는 P(Partition)는 필수"
따라서 진짜 선택은 CP vs AP
ILIC 관점:
자기 점검
선수 지식: Unit 6.2
핵심 개념
CAP의 한계:
PACELC:
"Partition 발생 시 Availability vs Consistency, Else 정상 시 Latency vs Consistency"
두 가지 분기:
PA/PC: 네트워크 장애 시 (CAP과 동일)
EL/EC: 정상 시
대표 시스템 분류:
| 시스템 | 분류 | 특징 |
|---|---|---|
| MongoDB | PA + EC | 장애 시 가용성, 정상 시 일관성 |
| Cassandra | PA + EL | 장애 시 가용성, 정상 시 지연 우선 |
| HBase, Spanner | PC + EC | 장애 시·정상 시 모두 일관성 우선 |
| DynamoDB | PA + EL | Cassandra 유사 |
| MySQL (단일) | (CA), EC | 장애 가정 X, 일관성 |
핵심 통찰:
"CAP는 장애 시만 다룬다. PACELC는 평상시 트레이드오프 도 본다"
ILIC 관점:
자기 점검
선수 지식: 4주차 Phase 4 (synchronized), 10주차 Phase 5
핵심 개념
왜 락이 필요한가:
2가지 락 종류 ⭐ :
공유 락 (Shared Lock, S Lock):
"여러 트랜잭션이 동시에 읽기 OK, 쓰기는 차단"
SELECT ... LOCK IN SHARE MODE베타 락 (Exclusive Lock, X Lock):
"한 트랜잭션만 데이터 접근, 다른 트랜잭션은 읽기조차 차단"
SELECT ... FOR UPDATE호환성 매트릭스:
| S Lock 요청 | X Lock 요청 | |
|---|---|---|
| S Lock 보유 중 | ✅ 호환 | ❌ 대기 |
| X Lock 보유 중 | ❌ 대기 | ❌ 대기 |
JPA에서의 활용 (참고):
@Lock(LockModeType.PESSIMISTIC_READ) // S Lock
@Lock(LockModeType.PESSIMISTIC_WRITE) // X Lock
격리 수준과의 관계 (10주차 Phase 5):
ILIC 시나리오:
자기 점검
목표: SQL이 어떻게 실행 계획으로 변환되는지 — DB의 두뇌를 이해한다.
선수 지식: Unit 5.1
핵심 정의
옵티마이저(Optimizer):
"SQL의 가장 효율적인 실행 경로를 생성하는 DBMS의 핵심 엔진"
비유:
"CPU가 컴퓨터의 두뇌라면, 옵티마이저는 DB의 두뇌"
동작 흐름:
1. 개발자가 SQL 작성
2. 옵티마이저: "이 쿼리를 어떻게 실행할까?"
3. 여러 실행 계획 생성
4. 각 계획의 비용 산정
5. 최저 비용 계획 선택
6. SQL 엔진이 실제 실행
2가지 옵티마이저 종류:
RBO (Rule-Based Optimizer) — 규칙 기반:
CBO (Cost-Based Optimizer) — 비용 기반 ⭐ :
자기 점검
선수 지식: Unit 7.1
핵심 6단계 ⭐ :
[SQL 입력]
↓
1. Parser — SQL 문법 검사 + 파싱 트리 생성
↓
2. Query Transformer — 서브쿼리 → JOIN, 불필요 조건 제거
↓
3. Estimator — 실행 비용 계산 (통계 기반)
↓
4. Plan Generator — 실행 계획 후보 생성 + 최적 선택
↓
5. Row-Source Generator — 실행 가능한 코드로 변환
↓
6. SQL Engine — 실제 실행
↓
[결과 반환]
Step 2 예시 — 쿼리 변환:
-- 입력
SELECT name FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE location = 'Seoul');
-- 옵티마이저가 변환
SELECT e.name FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE d.location = 'Seoul';
Step 3 — Estimator의 고려 요소:
1. 인덱스 존재 여부
2. 테이블 크기
3. 조인 방식 (Nested Loop, Hash Join, Merge Join)
4. 정렬 비용
통계 정보(Statistics):
갱신 방법:
1. 자동 갱신: DBMS 스케줄러
2. 수동 갱신: 대량 데이터 변경 후 즉시
ANALYZE TABLE employees;
통계가 오래된 경우:
자기 점검
선수 지식: Unit 7.2
옵티마이저 힌트:
"SQL 안에 주석 형태로 옵티마이저 동작 강제"
문법 (Oracle/MySQL 차이 있음):
SELECT /*+ HINT_NAME(table_name) */ * FROM table_name;
주요 힌트 ⭐ :
| 힌트 | 의미 |
|---|---|
/*+ FULL(t) */ | Full Table Scan 강제 |
/*+ INDEX(t idx) */ | 특정 인덱스 사용 강제 |
/*+ ORDERED */ | FROM 절 순서대로 조인 |
/*+ USE_HASH(t) */ | Hash Join 사용 |
/*+ PARALLEL(t, 4) */ | 병렬 처리 |
예시:
SELECT /*+ INDEX(emp idx_emp_name) */ *
FROM employees emp
WHERE name = 'John';
옵티마이저 모드:
"옵티마이저의 기본 정책 설정 — 전체 또는 세션 단위"
모드 종류:
| 모드 | 의미 |
|---|---|
| CHOOSE | 통계 있으면 CBO, 없으면 RBO (구식) |
| FIRST_ROWS | 첫 결과 빠르게 (인터랙티브 UI) |
| FIRST_ROWS(n) | 처음 n개 행 빠르게 |
| ALL_ROWS | 전체 결과 빠르게 (배치 처리, Oracle 10g+ 기본) |
힌트 vs 모드:
ILIC 적용 가능:
자기 점검
목표: DB 성능의 핵심인 인덱스를 빅오 표기법부터 B-tree 구조까지 깊이 이해한다.
선수 지식: 자료구조 일반 지식
핵심 개념
빅오 표기법:
"알고리즘의 시간 복잡도 — 입력 크기 N에 따른 연산 횟수"
주요 복잡도:
| 표기 | 의미 | 예 |
|---|---|---|
| O(1) | 상수 — 입력 무관 | HashMap.get() |
| O(logN) | 로그 — 절반씩 줄임 | 이진 탐색, B-tree |
| O(N) | 선형 — 비례 | Full Scan |
| O(N²) | 제곱 — 중첩 반복 | Nested Loop |
logN의 직관:
숫자 비교 (N = 100):
N = 1,000,000 (백만):
핵심 통찰:
"큰 N일수록 logN의 위력이 폭발적"
→ 인덱스가 데이터가 많을 때 더 효과적인 이유.
자기 점검
선수 지식: Unit 8.1
핵심 개념
인덱스(Index):
"검색 성능을 위한 자료구조 — 책의 목차 같은 역할"
Full Scan vs Index:
인덱스가 없을 때:
SELECT * FROM users WHERE first_name = 'Minsoo';
-- → 100만 행 전체 비교 (Full Scan)
인덱스가 있을 때:
CREATE INDEX idx_first_name ON users(first_name);
-- → 인덱스 트리에서 약 20번 비교 → 실제 행 위치 확인
왜 인덱스를 쓰는가 (2가지 이유):
1. WHERE 조건 빠른 검색
2. 정렬(ORDER BY)/그룹핑(GROUP BY) 가속
인덱스 생성 문법:
일반 인덱스:
CREATE INDEX player_name_idx ON player(name);
-- ↑ 인덱스 이름 ↑ 컬럼
유니크 인덱스 (멀티 컬럼 = 복합 인덱스):
CREATE UNIQUE INDEX team_id_backnumber_idx
ON player (team_id, backnumber);
테이블 생성 시:
CREATE TABLE player (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
INDEX idx_name (name)
);
Primary Key는 자동 인덱스.
인덱스 정보 확인:
SHOW INDEX FROM player;
Seq_in_index 컬럼:
자기 점검
선수 지식: Unit 8.2
핵심 개념
B-tree 인덱스의 본질:
"정렬된 별도 테이블 + 포인터 — 이진 탐색으로 O(logN)"
구조 시각화:
[원본 테이블] [인덱스 테이블 (정렬됨)]
+----+----+----+ +----+----------+
| id | a | b | | a | pointer |
+----+----+----+ +----+----------+
| 1 | 5 | 80 | | 1 | → row 7 |
| 2 | 3 | 60 | | 2 | → row 9 |
| 3 | 8 | 95 | ←CREATE→ | 3 | → row 2 |
| 4 | 1 | 30 | INDEX(a) | 5 | → row 1 |
| 5 | 9 | 70 | | 6 | → row 8 |
| 6 | 2 | 40 | | 8 | → row 3 |
| 7 | 1 | 20 | | 9 | → row 5 |
+----+----+----+ +----+----------+
조회 흐름 (WHERE a = 9):
1. 인덱스 테이블에서 이진 탐색으로 a=9 찾기 (O(logN))
2. 포인터 따라 원본 테이블 row 5 접근
3. → 해당 행 반환
멀티컬럼 인덱스 (Multi-column Index) ⭐ :
CREATE INDEX idx_a_b ON table(a, b);
정렬 기준:
1. a 컬럼 오름차순
2. a 같은 값들 안에서 b 오름차순
예시:
a | b
--+----
1 | 20 ← a 정렬 후
1 | 95
3 | 60
5 | 80
7 | 80
7 | 95 ← 같은 a=7 안에서 b로 정렬
8 | 95
9 | 70
효율적 사용 — WHERE a=7 AND b=95:
1. 인덱스에서 a=7 영역 탐색 (이진)
2. 그 안에서 b=95 탐색 (이진 — 이미 정렬됨)
3. → 완전히 효율적! ✅
비효율 사용 — WHERE b=95:
비효율 사용 — WHERE a=7 OR c=400:
핵심 원칙 ⭐ :
"왼쪽 컬럼 우선 — 멀티컬럼 인덱스는 왼쪽부터 활용 가능한 쿼리에만 효과"
ILIC 적용:
WHERE booking_date AND status 자주 → INDEX(booking_date, status)WHERE status 만 자주 → INDEX(status) 별도 필요자기 점검
WHERE a > 5 는 효과적인가? (힌트: YES)WHERE a > 5 AND b = 80 는? (힌트: a까지만 — b는 정렬 안 됨)목표: 실행 계획을 직접 분석하고 인덱스 선택을 제어한다.
선수 지식: Phase 8
핵심 시나리오
ILIC player 테이블에 여러 인덱스 존재:
(id) — Primary Key 자동(name)(team_id, backnumber)(backnumber)쿼리:
SELECT * FROM player WHERE backnumber = 7;
의문:
(team_id, backnumber) 와 (backnumber) 중 어떤 인덱스 사용?답: 옵티마이저가 결정 ⭐
일반적 선택:
(backnumber) 선호확인 방법:
EXPLAIN SELECT * FROM player WHERE backnumber = 7;
→ key 컬럼에 사용된 인덱스 표시
자기 점검
선수 지식: Unit 9.1
핵심 개념
EXPLAIN:
"SQL의 실행 계획을 미리 보여주는 명령"
MySQL 출력 주요 컬럼:
| 컬럼 | 의미 |
|---|---|
id | SELECT 실행 순서 |
select_type | SIMPLE, SUBQUERY, UNION 등 |
table | 대상 테이블 |
type | 접근 방식 (가장 중요!) |
possible_keys | 사용 가능한 인덱스들 |
key | 실제 사용된 인덱스 |
key_len | 인덱스 사용 길이 |
ref | 비교 대상 |
rows | 검사 예상 행 수 |
Extra | 추가 정보 (Using index, filesort 등) |
type 컬럼 — 가장 중요 ⭐ :
좋은 순 → 나쁜 순:
| type | 의미 | 평가 |
|---|---|---|
const | PK/UNIQUE로 1행 | ⭐⭐⭐ |
eq_ref | UNIQUE JOIN | ⭐⭐⭐ |
ref | 인덱스 비유니크 일치 | ⭐⭐ |
range | 인덱스 범위 검색 | ⭐⭐ |
index | 인덱스 풀 스캔 | ⭐ |
ALL | 테이블 풀 스캔 ❌ | 위험 |
Extra 컬럼 주요 값:
Using index — Covering Index (Phase 10)Using where — WHERE 조건 적용Using filesort — 별도 정렬 (성능 ↓)Using temporary — 임시 테이블 (성능 ↓)ILIC 활용:
EXPLAIN SELECT * FROM bookings WHERE customer_id = ?;
-- type = ref → 인덱스 사용 ✅
-- type = ALL → Full Scan ❌ → 인덱스 추가 검토
자기 점검
Using filesort가 보이면 어떻게 해결? (힌트: ORDER BY 컬럼에 인덱스)type = ALL 이지만 데이터가 적어 빠른 경우는? (힌트: 카디널리티 낮음)선수 지식: Unit 9.2, 7.3
핵심 개념
왜 인덱스 힌트?:
3가지 힌트 ⭐ :
1. USE INDEX — 사용 유도 (느슨):
SELECT * FROM player USE INDEX (backnumber_idx)
WHERE backnumber = 7;
2. FORCE INDEX — 강제 사용 (강함) ⭐ :
SELECT * FROM player FORCE INDEX (backnumber_idx)
WHERE backnumber = 7;
3. IGNORE INDEX — 무시 강제:
SELECT * FROM employees IGNORE INDEX (idx_emp_name)
WHERE name = 'John';
언제 사용하는가:
⚠️ 남용 주의:
옵티마이저 힌트 vs 인덱스 힌트 (비교):
| 옵티마이저 힌트 | 인덱스 힌트 | |
|---|---|---|
| 범위 | 실행 계획 전체 | 인덱스 사용만 |
| 예 | /*+ ORDERED */ | FORCE INDEX (...) |
| 강제력 | 옵티마이저가 무시 가능 | FORCE는 강제 |
| 표기 | 주석 (/*+ ... */) | 키워드 (USE/FORCE/IGNORE) |
ILIC 활용:
자기 점검
목표: 실무 인덱스 설계의 베스트 프랙티스 — 무엇을 할까, 무엇을 피할까.
선수 지식: Phase 9
핵심 개념
Covering Index:
"쿼리가 필요한 모든 컬럼을 인덱스가 포함하는 경우"
예시:
인덱스: (team_id, backnumber)
SELECT team_id, backnumber FROM player WHERE team_id = 1;
→ 조회 컬럼(team_id, backnumber) 모두 인덱스에 포함
→ 실제 테이블 접근 불필요 ⭐
→ 인덱스만으로 결과 반환 = Covering Index
장점:
EXPLAIN 결과:
Extra 컬럼에 Using index 표시Non-Covering 사례:
SELECT * FROM player WHERE team_id = 1;
-- '*' = 모든 컬럼 → 인덱스 외 컬럼도 필요 → 테이블 접근
Covering 만들기 — 인덱스 확장:
-- 자주 SELECT name도 한다면
CREATE INDEX idx_team_back_name ON player(team_id, backnumber, name);
ILIC 활용:
(status, created_date, fare_id) 인덱스⚠️ 주의:
자기 점검
SELECT * 가 Covering Index를 깨는 이유는?선수 지식: Unit 10.1
핵심 개념
Full Scan이 더 좋은 경우:
데이터가 적은 테이블 (몇십~몇백 건)
조회 데이터가 테이블의 큰 비율 (예: 30% 이상)
카디널리티 매우 낮음
옵티마이저의 판단:
인덱스의 비용 ⭐ :
1. WRITE 시 모든 인덱스 갱신:
2. 추가 저장 공간:
3. 인덱스 정렬 비용:
원칙 ⭐ :
"필요한 만큼만, 최소한의 인덱스"
중복 인덱스 피하기:
(team_id, backnumber) 있으면 (team_id) 불필요대용량 테이블 인덱스 추가 주의 ⭐ :
ALGORITHM=INPLACE MySQL 5.6+)ILIC 시나리오:
자기 점검
선수 지식: Phase 9, 10
최종 비교 매트릭스 ⭐ :
| 측면 | 옵티마이저 힌트 | 인덱스 힌트 |
|---|---|---|
| 범위 | 전체 실행 계획 (조인 순서, 병렬 등) | 인덱스 사용만 |
| 표기 | /*+ ORDERED */ (주석) | FORCE INDEX (idx) (키워드) |
| 강제력 | 옵티마이저가 무시 가능 | FORCE는 거의 절대 |
| 표준화 | DBMS마다 다름 | DBMS마다 다름 |
| Oracle | 매우 다양 | 적음 |
| MySQL | 적음 | 풍부 (USE/FORCE/IGNORE) |
힌트 사용 가이드라인:
실무 권장 흐름:
[성능 이슈] → ANALYZE TABLE → EXPLAIN → 인덱스 추가 → 힌트
(1순위) (분석) (2순위) (마지막)
ILIC 적용:
자기 점검
WHERE b=... 가 비효율적인 이유는?type 컬럼에서 좋은 값과 나쁜 값은?Extra 컬럼의 Using filesort 가 의미하는 바와 해결책은?★★★ 면접·실무 단골 (반드시):
★★ 매우 권장:
[ Part A — DB 이론과 관리 ]
[ ] Phase 1 — DB 핵심 용어와 모델링 (Unit 1.1~1.3)
[ ] Phase 2 — 정규화 (Unit 2.1~2.4)
[ ] Phase 3 — 이상 현상과 정규화 평가 (Unit 3.1~3.2)
[ ] Phase 4 — SQL Injection 보안 (Unit 4.1~4.2)
[ ] Phase 5 — RDBMS와 NoSQL (Unit 5.1~5.4)
[ ] Phase 6 — 분산 시스템 이론 + 락 (Unit 6.1~6.4)
[ Part B — SQL 성능 최적화 ]
[ ] Phase 7 — 옵티마이저 (Unit 7.1~7.3)
[ ] Phase 8 — 인덱스 기초 (Unit 8.1~8.3) ★ 정점
[ ] Phase 9 — 멀티컬럼 인덱스와 EXPLAIN (Unit 9.1~9.3)
[ ] Phase 10 — 인덱스 최적화 전략 (Unit 10.1~10.3)
[ ] 종합 자기 점검 38문항 통과
왜 정점인가:
학습 시 강조점:
12주차까지가 자바·Spring·JPA의 위쪽 추상화 였다면, 13주차는 DB 자체의 깊이 로 내려갔다.
| 영역 | 주차 | 추상화 수준 |
|---|---|---|
| 자바 언어 | 1~3주차 | 가장 높음 |
| 동시성·Spring | 4~5주차 | 높음 |
| Spring DB 접근 | 6~7주차 | 중상 |
| AOP·트랜잭션 | 8~10주차 | 중상 |
| JPA·영속성 컨텍스트 | 11~12주차 | 중 |
| DB 펀더멘털 | 13주차 (지금) | 가장 낮음 (DB 자체) |
의의:
이번 주차는 이론만 읽으면 절대 체화되지 않습니다:
특히 Phase 8 (B-tree) 와 Phase 9 (EXPLAIN) 은 ILIC 실제 데이터베이스에서:
EXPLAIN SELECT * FROM bookings WHERE customer_id = ? AND status = ?;
EXPLAIN ANALYZE ... -- 실제 시간 측정 (PostgreSQL/MySQL 8.0+)
이런 식으로 직접 검증하세요. 1~2시간 EXPLAIN 다양한 쿼리에 돌려보면 인덱스 감각이 폭발적으로 늘어납니다.