- Published on
- •👁️
DataBase
- Authors

- Name
- River
26데이터베이스 인덱스에 대해서 설명해주세요.보통
데이터베이스 인덱스는 테이블의 검색 속도를 향상시키기 위해 사용되는 데이터 구조입니다. 인덱스는 특정 열에 대해 정렬된 구조를 유지하여 전체 테이블 스캔 없이 빠른 데이터 검색을 가능하게 합니다. 주로 B-Tree 구조로 구현되며, 클러스터형과 비클러스터형으로 분류됩니다. 인덱스는 SELECT 성능을 크게 향상시키지만, INSERT/UPDATE/DELETE 시에는 추가적인 오버헤드가 발생합니다. 실무에서는 WHERE 절에 자주 사용되는 컬럼이나 JOIN 조건에 사용되는 컬럼에 인덱스를 생성하는 것이 일반적입니다.
상세 설명
데이터베이스 인덱스란?
- 데이터베이스 테이블의 검색 속도를 향상시키기 위해 사용되는 데이터 구조
- 특정 열(컬럼)에 대해 정렬된 구조를 유지하여 빠른 데이터 접근을 가능하게 함
- 책의 목차나 색인과 같은 역할
- 테이블과는 별도로 저장되며, 실제 데이터의 위치를 가리키는 포인터를 포함
인덱스의 동작 원리
- 인덱스 없이 검색 (Full Table Scan)
SELECT * FROM users WHERE email = 'john@example.com';- 테이블의 모든 행을 순차적으로 검사
- 시간 복잡도: O(n)
- 1억 개 레코드가 있다면 최악의 경우 1억 번 비교
- 인덱스를 사용한 검색 (Index Seek)
CREATE INDEX idx_email ON users(email); SELECT * FROM users WHERE email = 'john@example.com';- B-Tree 구조를 통해 빠른 탐색
- 시간 복잡도: O(log n)
- 1억 개 레코드에서도 약 27번의 비교로 검색 완료
인덱스의 구조
- B-Tree (Balanced Tree)
Root Node ├── Branch Node (A-M) │ ├── Leaf Node (A-D) │ └── Leaf Node (E-M) └── Branch Node (N-Z) ├── Leaf Node (N-S) └── Leaf Node (T-Z)- 가장 일반적인 인덱스 구조
- 균형 잡힌 트리로 모든 리프 노드가 같은 깊이
- 범위 검색에 효율적
- Hash Index
-- MySQL에서 MEMORY 엔진 사용 시 CREATE TABLE hash_table ( id INT, name VARCHAR(50), INDEX USING HASH (id) ) ENGINE=MEMORY;- 해시 함수를 사용하여 키를 해시값으로 변환
- 등가 연산(=)에 매우 빠름
- 범위 검색에는 부적합
인덱스 종류
- 클러스터드 인덱스 (Clustered Index)
-- MySQL에서 PRIMARY KEY는 자동으로 클러스터드 인덱스 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), email VARCHAR(100) );- 테이블당 하나만 존재 가능
- 실제 데이터가 인덱스 키 순서대로 물리적으로 저장
- Primary Key가 기본적으로 클러스터드 인덱스
- 논클러스터드 인덱스 (Non-Clustered Index)
CREATE INDEX idx_email ON users(email); CREATE INDEX idx_name ON users(name);- 테이블당 여러 개 생성 가능
- 인덱스와 실제 데이터가 별도로 저장
- 인덱스에서 실제 데이터 위치를 참조
- 복합 인덱스 (Composite Index)
CREATE INDEX idx_name_age ON users(name, age); -- 효율적으로 사용되는 쿼리 SELECT * FROM users WHERE name = 'John' AND age = 25; SELECT * FROM users WHERE name = 'John'; -- 첫 번째 컬럼만 사용 -- 비효율적인 쿼리 SELECT * FROM users WHERE age = 25; -- 두 번째 컬럼만 사용- 여러 컬럼을 조합한 인덱스
- 컬럼 순서가 중요 (Leftmost Prefix Rule)
인덱스 생성 전략
- 언제 인덱스를 생성해야 할까?
- WHERE 절에 자주 사용되는 컬럼
- JOIN 조건에 사용되는 컬럼
- ORDER BY 절에 자주 사용되는 컬럼
- 카디널리티(고유값의 개수)가 높은 컬럼
- 실무 예제
-- 사용자 테이블에서 자주 사용되는 쿼리 패턴 분석 -- 1. 이메일로 로그인 SELECT * FROM users WHERE email = ?; CREATE INDEX idx_email ON users(email); -- 2. 활성 사용자 중 특정 역할 검색 SELECT * FROM users WHERE status = 'ACTIVE' AND role = 'ADMIN'; CREATE INDEX idx_status_role ON users(status, role); -- 3. 생성일 기준 정렬 SELECT * FROM users ORDER BY created_at DESC; CREATE INDEX idx_created_at ON users(created_at);
인덱스의 장단점
- 장점
- SELECT 성능 대폭 향상
- ORDER BY, GROUP BY 성능 향상
- MIN, MAX 함수 성능 향상
- 유니크 제약조건 강화
- 단점
- 추가 저장 공간 필요 (테이블 크기의 10-15%)
- INSERT/UPDATE/DELETE 성능 저하
- 인덱스 유지보수 비용
- 잘못된 인덱스는 오히려 성능 저하
인덱스 성능 최적화
- Covering Index
-- 쿼리에서 필요한 모든 컬럼을 인덱스에 포함 CREATE INDEX idx_covering ON users(email, name, status); SELECT name, status FROM users WHERE email = 'john@example.com'; -- 테이블에 접근하지 않고 인덱스에서만 데이터 조회
- 인덱스 힌트 사용
-- MySQL에서 특정 인덱스 강제 사용 SELECT * FROM users USE INDEX(idx_email) WHERE email = 'john@example.com';
- 실행 계획 분석
EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';
중요
실무에서는 인덱스를 무작정 많이 생성하기보다는, 실제 쿼리 패턴을 분석하여 필요한 인덱스만 선별적으로 생성하는 것이 중요합니다. 또한 정기적으로 사용되지 않는 인덱스를 제거하여 성능과 저장공간을 최적화해야 합니다.
27관계형 데이터베이스와 비 관계형 데이터베이스의 차이점은 무엇인가요?보통
관계형 데이터베이스(RDBMS)는 테이블 형식으로 데이터를 저장하고 SQL을 사용하여 데이터를 조작하며, ACID 속성을 보장하여 강한 일관성을 제공합니다. 반면 비관계형 데이터베이스(NoSQL)는 문서, 키-값, 컬럼, 그래프 등 다양한 형태로 데이터를 저장하며, 스키마가 유연하고 수평적 확장성이 뛰어납니다. RDBMS는 금융 시스템처럼 데이터 일관성이 중요한 경우에 적합하고, NoSQL은 소셜미디어나 IoT처럼 대용량 데이터와 빠른 확장이 필요한 경우에 유리합니다. 실무에서는 각각의 장단점을 고려하여 적절한 데이터베이스를 선택하거나 폴리글랏 퍼시스턴스 패턴을 활용합니다.
상세 설명
RDBMS vs NoSQL 개요
- RDBMS (Relational Database Management System)
- 테이블 간의 관계를 기반으로 데이터를 구조화
- SQL(Structured Query Language) 사용
- 스키마 기반의 엄격한 데이터 구조
- ACID 속성 보장
- NoSQL (Not Only SQL)
- 다양한 데이터 모델 지원 (문서, 키-값, 컬럼, 그래프)
- 스키마가 유연하거나 없음
- 수평적 확장성(Scale-out)에 최적화
- Eventually Consistent 모델
데이터 모델 비교
- RDBMS 데이터 모델
-- Users 테이블 CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100) UNIQUE, created_at TIMESTAMP ); -- Orders 테이블 CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT, product_name VARCHAR(200), amount DECIMAL(10,2), FOREIGN KEY (user_id) REFERENCES users(id) );- 정규화된 테이블 구조
- 외래키를 통한 관계 정의
- 스키마 변경이 어려움
- NoSQL 데이터 모델 (문서형 - MongoDB)
// 사용자와 주문 정보를 하나의 문서로 저장 { "_id": ObjectId("..."), "name": "John Doe", "email": "john@example.com", "created_at": ISODate("2024-01-01"), "orders": [ { "id": "ord_001", "product_name": "Laptop", "amount": 1299.99, "order_date": ISODate("2024-01-15") }, { "id": "ord_002", "product_name": "Mouse", "amount": 29.99, "order_date": ISODate("2024-01-20") } ] }- 중첩된 구조로 관련 데이터 저장
- 스키마가 유연함
- JOIN 없이 한 번의 쿼리로 조회
NoSQL 데이터베이스 유형
- 문서형 (Document Store)
// MongoDB 예제 db.users.insertOne({ name: "Alice", age: 30, address: { street: "123 Main St", city: "Seoul", country: "Korea" }, hobbies: ["reading", "swimming"] });- JSON/BSON 형태의 문서 저장
- 중첩 구조와 배열 지원
- 예: MongoDB, CouchDB
- 키-값 저장소 (Key-Value Store)
// Redis 예제 SET user:1001:profile "{'name':'John','email':'john@example.com'}" SET user:1001:session_token "abc123xyz" EXPIRE user:1001:session_token 3600 // 1시간 후 만료- 단순한 키-값 쌍
- 매우 빠른 읽기/쓰기
- 예: Redis, DynamoDB
- 컬럼형 (Column-Family)
-- Cassandra 예제 CREATE TABLE user_activities ( user_id UUID, activity_time TIMESTAMP, activity_type TEXT, details TEXT, PRIMARY KEY (user_id, activity_time) );- 컬럼별로 데이터 저장
- 대용량 데이터 처리에 적합
- 예: Cassandra, HBase
- 그래프형 (Graph Database)
// Neo4j 예제 CREATE (alice:Person {name: 'Alice', age: 30}) CREATE (bob:Person {name: 'Bob', age: 25}) CREATE (alice)-[:FRIENDS_WITH]->(bob) // 친구의 친구 찾기 MATCH (p:Person {name: 'Alice'})-[:FRIENDS_WITH*2]-(friend_of_friend) RETURN friend_of_friend.name- 노드와 엣지로 관계 표현
- 복잡한 관계 쿼리에 특화
- 예: Neo4j, Amazon Neptune
ACID vs BASE
ACID (RDBMS)
- Atomicity (원자성): 트랜잭션의 모든 연산이 성공하거나 모두 실패
- Consistency (일관성): 데이터베이스 규칙을 항상 만족
- Isolation (격리성): 동시 실행되는 트랜잭션들이 서로 영향을 주지 않음
- Durability (지속성): 커밋된 트랜잭션은 영구적으로 저장
-- 계좌 이체 예제 (ACID 보장) BEGIN TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT;
BASE (NoSQL)
- Basically Available: 시스템이 대부분의 시간에 사용 가능
- Soft State: 시스템 상태가 시간에 따라 변할 수 있음
- Eventually Consistent: 최종적으로는 일관성 달성
// MongoDB 예제 - 최종 일관성 // 글 작성 시 조회수는 즉시 반영되지 않을 수 있음 db.posts.updateOne( {_id: postId}, {$inc: {view_count: 1}} );
확장성 비교
- 수직적 확장 (Scale-up) - RDBMS 주로 사용
Before: CPU 4core, RAM 16GB, SSD 1TB After: CPU 16core, RAM 64GB, SSD 4TB- 하드웨어 성능 향상
- 확장 한계 존재
- 비용이 기하급수적으로 증가
- 수평적 확장 (Scale-out) - NoSQL 주로 사용
Before: Server 1대 After: Server 3대로 데이터 분산- 서버 수 증가로 처리 능력 향상
- 이론적으로 무제한 확장
- 비교적 저렴한 비용
실무 사용 사례
- RDBMS가 적합한 경우
-- 금융 시스템: 정확한 잔액 계산이 필수 SELECT account_id, SUM(CASE WHEN type = 'DEPOSIT' THEN amount ELSE -amount END) as balance FROM transactions WHERE account_id = ? GROUP BY account_id;- 금융 시스템 (정확한 계산, 트랜잭션 보장)
- ERP 시스템 (복잡한 비즈니스 로직)
- 전자상거래 주문 시스템
- NoSQL이 적합한 경우
// 실시간 로그 분석 시스템 db.user_events.insertMany([ {user_id: 1001, event: "page_view", url: "/products", timestamp: new Date()}, {user_id: 1002, event: "click", element: "buy_button", timestamp: new Date()}, {user_id: 1001, event: "purchase", product_id: "prod_123", timestamp: new Date()} ]);- 소셜 미디어 (대용량 사용자 콘텐츠)
- IoT 데이터 수집 (센서 데이터)
- 실시간 분석 시스템
- 콘텐츠 관리 시스템
폴리글랏 퍼시스턴스 (Polyglot Persistence)
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // MySQL (사용자 기본 정보)
@Autowired
private RedisTemplate redisTemplate; // Redis (세션, 캐시)
@Autowired
private MongoTemplate mongoTemplate; // MongoDB (활동 로그)
public void createUser(User user) {
// 1. 기본 정보는 MySQL에 저장
userRepository.save(user);
// 2. 세션 정보는 Redis에 저장
redisTemplate.opsForValue().set("session:" + user.getId(),
user.getSessionToken(), 30, TimeUnit.MINUTES);
// 3. 사용자 활동 로그는 MongoDB에 저장
UserActivity activity = new UserActivity(user.getId(), "USER_CREATED", new Date());
mongoTemplate.save(activity);
}
}
팁
실무에서는 하나의 데이터베이스만 사용하기보다는, 각 요구사항에 맞는 적절한 데이터베이스를 조합하여 사용하는 폴리글랏 퍼시스턴스 패턴이 많이 활용됩니다.
선택 기준
| 구분 | RDBMS | NoSQL |
|---|---|---|
| 데이터 일관성 | 강한 일관성 (ACID) | 최종 일관성 (BASE) |
| 스키마 | 고정된 스키마 | 유연한 스키마 |
| 확장성 | 수직적 확장 | 수평적 확장 |
| 쿼리 | SQL (표준화됨) | DB별 고유 쿼리 |
| 적합한 용도 | 금융, ERP, 전자상거래 | 소셜미디어, IoT, 빅데이터 |
데이터베이스 정규화는 데이터 중복을 최소화하고 데이터 무결성을 보장하기 위해 테이블을 체계적으로 분해하는 과정입니다. 주요 정규화 단계로는 1NF(원자값), 2NF(부분 함수 종속 제거), 3NF(이행적 함수 종속 제거)가 있습니다. 1NF는 모든 속성이 원자값을 가져야 하고, 2NF는 기본키가 아닌 모든 속성이 기본키에 완전 함수 종속되어야 하며, 3NF는 기본키가 아닌 속성들 간의 이행적 종속을 제거해야 합니다. 정규화를 통해 삽입/수정/삭제 이상현상을 방지할 수 있지만, 과도한 정규화는 조회 성능 저하를 야기할 수 있어 실무에서는 적절한 균형점을 찾는 것이 중요합니다.
상세 설명
데이터베이스 정규화란?
- 데이터베이스 설계 과정에서 데이터 중복을 최소화하고 데이터 무결성을 보장하기 위해 테이블을 체계적으로 분해하는 과정
- 잘못 설계된 테이블에서 발생하는 이상현상(Anomaly)을 제거
- 함수적 종속성(Functional Dependency) 개념을 기반으로 수행
- Edgar F. Codd가 제안한 관계형 데이터베이스의 핵심 이론
정규화가 필요한 이유
- 정규화 이전 테이블의 문제점
-- 비정규화된 학생 수강 테이블 CREATE TABLE student_courses ( student_id INT, student_name VARCHAR(50), student_major VARCHAR(50), course_id VARCHAR(10), course_name VARCHAR(100), instructor VARCHAR(50), grade CHAR(1) ); -- 데이터 예시 INSERT INTO student_courses VALUES (1001, 'Alice', 'Computer Science', 'CS101', 'Programming', 'Dr. Kim', 'A'), (1001, 'Alice', 'Computer Science', 'CS201', 'Data Structure', 'Dr. Lee', 'B'), (1002, 'Bob', 'Mathematics', 'CS101', 'Programming', 'Dr. Kim', 'A');
- 발생하는 이상현상
삽입 이상(Insertion Anomaly)
- 새로운 강의를 개설하려면 학생이 수강해야만 입력 가능
- 학생 정보 없이는 강의 정보만 저장할 수 없음
수정 이상(Update Anomaly)
- 학생의 전공이 변경되면 여러 행을 모두 수정해야 함
- 일부만 수정하면 데이터 불일치 발생
삭제 이상(Deletion Anomaly)
- 학생이 모든 수강을 취소하면 학생 정보도 함께 삭제됨
- 강의 정보도 의도치 않게 손실될 수 있음
함수적 종속성 (Functional Dependency)
- 정의
- 속성 A의 값이 결정되면 속성 B의 값도 유일하게 결정되는 관계
- 표기법: A → B (A가 B를 함수적으로 결정한다)
- 예시
-- 학생 테이블에서 student_id → student_name, student_major, student_email -- 학번이 결정되면 학생의 이름, 전공, 이메일이 유일하게 결정됨 -- 강의 테이블에서 course_id → course_name, instructor, credits -- 강의 코드가 결정되면 강의명, 교수, 학점이 유일하게 결정됨
- 종속성 유형
- 완전 함수 종속: 기본키 전체에 종속
- 부분 함수 종속: 기본키의 일부에만 종속
- 이행적 종속: A → B, B → C이면 A → C
제1정규형 (1NF, First Normal Form)
- 조건
- 모든 속성의 값이 원자값(Atomic Value)이어야 함
- 반복 그룹이 없어야 함
- 각 행은 고유해야 함
- 1NF 위반 예시
-- 1NF 위반: 전화번호가 원자값이 아님 CREATE TABLE employees_before_1nf ( emp_id INT, name VARCHAR(50), phone_numbers VARCHAR(200), -- "010-1234-5678, 02-987-6543" skills VARCHAR(200) -- "Java, Python, SQL" );
- 1NF 적용 후
-- 1NF 적용: 원자값으로 분리 CREATE TABLE employees ( emp_id INT, name VARCHAR(50), PRIMARY KEY (emp_id) ); CREATE TABLE employee_phones ( emp_id INT, phone_type VARCHAR(20), -- 'mobile', 'office' phone_number VARCHAR(20), PRIMARY KEY (emp_id, phone_type), FOREIGN KEY (emp_id) REFERENCES employees(emp_id) ); CREATE TABLE employee_skills ( emp_id INT, skill VARCHAR(50), PRIMARY KEY (emp_id, skill), FOREIGN KEY (emp_id) REFERENCES employees(emp_id) );
제2정규형 (2NF, Second Normal Form)
- 조건
- 1NF를 만족해야 함
- 부분 함수 종속을 제거해야 함
- 기본키가 아닌 모든 속성이 기본키에 완전 함수 종속되어야 함
- 2NF 위반 예시
-- 2NF 위반: 복합키 (student_id, course_id)에서 부분 종속 발생 CREATE TABLE enrollments_before_2nf ( student_id INT, course_id VARCHAR(10), student_name VARCHAR(50), -- student_id에만 종속 course_name VARCHAR(100), -- course_id에만 종속 instructor VARCHAR(50), -- course_id에만 종속 grade CHAR(1), -- 전체 키에 종속 PRIMARY KEY (student_id, course_id) );student_name은student_id에만 종속 (부분 함수 종속)course_name,instructor는course_id에만 종속 (부분 함수 종속)
- 2NF 적용 후
-- 2NF 적용: 부분 종속 제거 CREATE TABLE students ( student_id INT PRIMARY KEY, student_name VARCHAR(50) ); CREATE TABLE courses ( course_id VARCHAR(10) PRIMARY KEY, course_name VARCHAR(100), instructor VARCHAR(50) ); CREATE TABLE enrollments ( student_id INT, course_id VARCHAR(10), grade CHAR(1), PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES students(student_id), FOREIGN KEY (course_id) REFERENCES courses(course_id) );
제3정규형 (3NF, Third Normal Form)
- 조건
- 2NF를 만족해야 함
- 이행적 함수 종속을 제거해야 함
- 기본키가 아닌 속성들 간에 종속관계가 없어야 함
- 3NF 위반 예시
-- 3NF 위반: 이행적 종속 발생 CREATE TABLE employees_before_3nf ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept_id INT, dept_name VARCHAR(50), -- dept_id → dept_name (이행적 종속) dept_location VARCHAR(100) -- dept_id → dept_location (이행적 종속) );emp_id → dept_id → dept_name(이행적 종속)emp_id → dept_id → dept_location(이행적 종속)
- 3NF 적용 후
-- 3NF 적용: 이행적 종속 제거 CREATE TABLE departments ( dept_id INT PRIMARY KEY, dept_name VARCHAR(50), dept_location VARCHAR(100) ); CREATE TABLE employees ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50), dept_id INT, FOREIGN KEY (dept_id) REFERENCES departments(dept_id) );
정규화의 장단점
- 장점
- 데이터 중복 제거로 저장공간 절약
- 데이터 일관성 보장
- 이상현상 방지
- 데이터 무결성 향상
- 유지보수 용이성
- 단점
- 조인 연산 증가로 인한 성능 저하
- 쿼리 복잡도 증가
- 개발 복잡성 증대
실무에서의 정규화 vs 반정규화
- 정규화가 적합한 경우
-- OLTP 시스템: 트랜잭션 처리가 빈번한 시스템 -- 고객 주문 시스템 CREATE TABLE customers ( customer_id INT PRIMARY KEY, customer_name VARCHAR(100), email VARCHAR(100) ); CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT, order_date DATE, total_amount DECIMAL(10,2), FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ); CREATE TABLE order_items ( order_id INT, product_id INT, quantity INT, unit_price DECIMAL(8,2), PRIMARY KEY (order_id, product_id) );
- 반정규화가 적합한 경우
-- OLAP 시스템: 분석과 보고서를 위한 시스템 -- 매출 리포트용 테이블 (의도적 반정규화) CREATE TABLE sales_report ( order_id INT, order_date DATE, customer_id INT, customer_name VARCHAR(100), -- 중복 허용 customer_email VARCHAR(100), -- 중복 허용 product_id INT, product_name VARCHAR(200), -- 중복 허용 category VARCHAR(50), -- 중복 허용 quantity INT, unit_price DECIMAL(8,2), total_amount DECIMAL(10,2) );
고급 정규화 형태
- BCNF (Boyce-Codd Normal Form)
-- BCNF 위반 예시 CREATE TABLE course_instructors ( course_id VARCHAR(10), instructor VARCHAR(50), student_id INT, PRIMARY KEY (course_id, instructor, student_id) ); -- 문제: instructor → course_id 종속성이 있지만 instructor가 후보키가 아님 -- BCNF 적용 CREATE TABLE course_assignments ( instructor VARCHAR(50), course_id VARCHAR(10), PRIMARY KEY (instructor), UNIQUE (course_id, instructor) ); CREATE TABLE student_enrollments ( course_id VARCHAR(10), instructor VARCHAR(50), student_id INT, PRIMARY KEY (course_id, instructor, student_id), FOREIGN KEY (course_id, instructor) REFERENCES course_assignments(course_id, instructor) );
중요
실무에서는 3NF까지 적용하는 것이 일반적이며, 성능상의 이유로 의도적인 반정규화를 적용하기도 합니다. 중요한 것은 데이터의 성격과 사용 패턴을 고려하여 적절한 수준의 정규화를 적용하는 것입니다.
실무 적용 전략
- 단계별 접근법
- 요구사항 분석: 데이터 사용 패턴 파악
- 초기 설계: 3NF까지 정규화 적용
- 성능 테스트: 실제 데이터로 성능 측정
- 선택적 반정규화: 성능 향상이 필요한 부분만 반정규화
- 모니터링 포인트
-- 조인이 많은 쿼리의 성능 모니터링 EXPLAIN ANALYZE SELECT c.customer_name, o.order_date, oi.product_name, oi.quantity FROM customers c JOIN orders o ON c.customer_id = o.customer_id JOIN order_items oi ON o.order_id = oi.order_id WHERE o.order_date >= '2024-01-01';
- 인덱스를 통한 성능 최적화
-- 정규화된 테이블에서 조인 성능 향상 CREATE INDEX idx_orders_customer_id ON orders(customer_id); CREATE INDEX idx_order_items_order_id ON order_items(order_id); CREATE INDEX idx_orders_date ON orders(order_date);