Guider/Database/DatabaseDevGuide0003
Database#03

DatabaseDevGuide0003

SQL 심화 완전 정복

🗄️ DATABASE DEVELOPER GUIDE SERIES

DatabaseDevGuide0003

SQL 심화 완전 정복 A-Z

JOIN · 서브쿼리 · 인덱스 · 뷰 · 스토어드 프로시저 · 트리거까지
데이터베이스 개발자가 되기 위한 모든 것, A부터 Z까지 다 알려드립니다!

⏱ 예상 학습: 25~35시간 📊 난이도: 중급~고급 🎯 목표: 현업 즉시 투입

📋 전체 학습 목차

번호 학습 주제 핵심 키워드
01 JOIN - 테이블 합치기 완전 정복 INNER, LEFT, RIGHT, FULL
02 서브쿼리(Subquery) 완전 정복 스칼라, 인라인뷰, CTE
03 인덱스(Index) 완전 정복 - 검색 속도의 비밀 B-Tree, 복합, EXPLAIN
04 뷰(View) - 가상 테이블 활용 CREATE VIEW, 보안
05 스토어드 프로시저 & 함수 PROCEDURE, FUNCTION
06 트리거(Trigger) - 자동 실행 이벤트 BEFORE, AFTER, NEW/OLD
07 윈도우 함수(Window Function) ROW_NUMBER, RANK, LAG
08 트랜잭션 심화 & 잠금(Lock) 격리 수준, 데드락
09 쿼리 성능 최적화 실전 EXPLAIN, 실행계획
10 실전 프로젝트: 쇼핑몰 DB 완성 종합 실습
11 현업 면접 SQL 심화 Q&A TOP 10 질문
12 학습 로드맵 & 체크리스트 다음 단계
01

JOIN - 테이블 합치기 완전 정복

💡 JOIN이란?
여러 테이블의 데이터를 관계(FK)를 기준으로 합쳐서 하나의 결과로 보여주는 SQL 명령입니다.
현업에서 SELECT 다음으로 가장 많이 쓰이는 기능! 거의 모든 복잡한 쿼리에 JOIN이 등장합니다.

📊 JOIN의 종류 한눈에 비교

🔴
INNER JOIN
두 테이블의 교집합
양쪽에 모두 있는 데이터만 반환
가장 많이 사용!
🔵
LEFT JOIN
왼쪽 테이블 전체 +
오른쪽 일치 데이터
없으면 NULL
🟢
RIGHT JOIN
오른쪽 테이블 전체 +
왼쪽 일치 데이터
없으면 NULL
🟣
FULL OUTER JOIN
양쪽 테이블 모두 전체
합집합 (MySQL은 UNION으로)
없는 쪽은 NULL

🏫 실습용 샘플 테이블

👤 users 테이블 (회원)
id name email grade
1 홍길동 hong@test.com VIP
2 김영희 kim@test.com 일반
3 이철수 lee@test.com 골드
4 박민수 park@test.com VIP
🛒 orders 테이블 (주문)
order_id user_id product_name amount status
1001 1 노트북 1,500,000 완료
1002 2 마우스 35,000 완료
1003 1 키보드 85,000 배송중
1004 99 모니터 320,000 완료

✅ INNER JOIN - 교집합 (가장 기본!)

-- 회원과 주문을 합치기 (양쪽에 모두 있는 데이터만)
SELECT
    u.name AS 회원명,
    u.grade AS 등급,
    o.product_name AS 상품명,
    o.amount AS 금액,
    o.status AS 상태
FROM users u                    -- 왼쪽 테이블 (별칭 u)
INNER JOIN orders o             -- 오른쪽 테이블 (별칭 o)
    ON u.id = o.user_id;       -- 연결 조건 (FK = PK)

-- 📤 결과: 홍길동-노트북, 홍길동-키보드, 김영희-마우스 (3건)
-- user_id=99인 모니터 주문은 결과에 없음! (users에 id=99가 없음)
-- 박민수(id=4)도 없음! (orders에 user_id=4가 없음)

✅ LEFT JOIN - 왼쪽 테이블 기준 전체

-- 모든 회원 목록 + 주문 있으면 주문 정보도 표시
SELECT u.name, u.grade, o.product_name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- 결과: 홍길동-노트북, 홍길동-키보드, 김영희-마우스, 이철수-NULL, 박민수-NULL (5건)

-- 🔥 현업 응용: 한 번도 주문 안 한 회원 찾기
SELECT u.name, u.email
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.order_id IS NULL;

✅ 3개 이상 테이블 JOIN (현업 필수!)

-- users + orders + products 3개 테이블 조인
SELECT u.name, u.grade, o.order_date, p.product_name, p.category, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN products p ON o.product_id = p.id
WHERE o.status = '완료'
ORDER BY o.order_date DESC;

-- 💡 팁: JOIN을 여러 번 체인처럼 연결 가능!
-- 조인 순서: FROM → 첫번째 JOIN → 두번째 JOIN → ...
-- 반드시 테이블 별칭(u, o, p)을 사용하고
-- ON 조건에 인덱스가 있는 컬럼을 써야 성능이 좋습니다!
⚠️ JOIN 현업 주의사항
1. 반드시 테이블에 별칭(alias)을 붙이세요 (u, o, p...) → 컬럼 충돌 방지
2. ON 조건에 인덱스가 있는 컬럼을 사용해야 속도가 빠릅니다
3. JOIN 결과가 예상보다 많다면 카테시안 곱이 발생한 것! ON 조건을 확인하세요
4. NULL 처리: LEFT JOIN 후 오른쪽이 NULL인 행은 COALESCE()로 처리
02

서브쿼리(Subquery) 완전 정복

💡 서브쿼리란?
SQL 쿼리 안에 또 다른 SQL 쿼리가 들어있는 구조입니다. 쿼리 안의 쿼리!
복잡한 조건을 단계적으로 해결할 때 강력하게 사용됩니다.

📊 서브쿼리 3가지 종류

종류 위치 반환값 특징
스칼라 서브쿼리 SELECT 절 단일 값(1행 1열) 컬럼처럼 사용
인라인 뷰 FROM 절 가상 테이블 테이블처럼 사용
중첩/상관 서브쿼리 WHERE/HAVING 절 단일 또는 다중 값 조건 필터링
-- 예제 1: 평균 금액보다 비싼 주문만 조회
SELECT product_name, amount
FROM orders
WHERE amount > (SELECT AVG(amount) FROM orders);

-- 예제 2: IN + 서브쿼리 (VIP 회원의 주문만 조회)
SELECT product_name, amount, status
FROM orders
WHERE user_id IN (
    SELECT id FROM users WHERE grade = 'VIP'
);

-- 예제 3: EXISTS (데이터 존재 여부 체크) - 성능 좋음!
SELECT u.name, u.email
FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o
    WHERE o.user_id = u.id AND o.status = '완료'
);

-- 예제 4: FROM절 서브쿼리 (인라인 뷰)
SELECT * FROM (
    SELECT u.name, COUNT(o.order_id) AS 주문횟수, SUM(o.amount) AS 총주문액
    FROM users u JOIN orders o ON u.id = o.user_id
    GROUP BY u.id, u.name
) AS summary
WHERE 총주문액 >= 1000000 ORDER BY 총주문액 DESC;

-- 예제 5: CTE (WITH절) - 현업 최신 트렌드!
WITH vip_users AS (
    SELECT id, name FROM users WHERE grade = 'VIP'
),
vip_orders AS (
    SELECT u.name, COUNT(*) AS cnt, SUM(o.amount) AS total
    FROM vip_users u JOIN orders o ON u.id = o.user_id
    WHERE o.status = '완료'
    GROUP BY u.id, u.name
)
SELECT name, cnt AS 주문건수, total AS 총구매액
FROM vip_orders ORDER BY total DESC;
⚠️ 서브쿼리 vs JOIN vs CTE 선택 기준
단순 조건 필터링: 서브쿼리 (IN, EXISTS) → 가독성 좋음
양쪽 테이블 컬럼 모두 필요: JOIN → 성능 좋음
복잡한 다단계 처리: CTE (WITH절) → 현업 권장
EXISTS vs IN: 대용량에서는 EXISTS가 일반적으로 빠름
03

인덱스(Index) 완전 정복 - 검색 속도의 비밀

💡 인덱스란?
책의 찾아보기(색인)와 똑같은 원리입니다. 1000페이지 책에서 처음부터 읽는 대신 색인에서 페이지 번호를 찾아 바로 이동하는 것처럼, 인덱스는 수백만 건 데이터에서 원하는 행을 밀리초 내에 찾아줍니다!
상황 100만 건 1000만 건 방법
인덱스 없음 약 1~5초 약 10~60초 Full Table Scan
인덱스 있음 약 1ms 이하 약 1~5ms Index Scan
-- 1. 단일 인덱스 생성
CREATE INDEX idx_email ON users(email);

-- 2. 복합 인덱스 (여러 컬럼)
CREATE INDEX idx_name_grade ON users(name, grade);
-- WHERE name = '홍길동' AND grade = 'VIP' → 최적화!

-- 3. UNIQUE 인덱스
CREATE UNIQUE INDEX idx_unique_email ON users(email);

-- 4. 인덱스 목록 조회
SHOW INDEX FROM users;

-- 5. 인덱스 삭제
DROP INDEX idx_email ON users;

-- EXPLAIN으로 쿼리 실행 계획 분석!
EXPLAIN SELECT * FROM users WHERE email = 'hong@test.com';
-- type 컬럼이 핵심!
-- const   → PK/UNIQUE로 1건 검색 (최고!)
-- ref     → 인덱스로 검색 (좋음)
-- range   → 인덱스 범위 검색
-- ALL     → 전체 테이블 스캔 (최악! 반드시 개선!)

📋 인덱스 설계 황금 법칙

인덱스 걸어야 할 곳 ✅ 인덱스 걸지 말아야 할 곳 ❌
WHERE 조건에 자주 쓰이는 컬럼 데이터가 적은 테이블 (100건 이하)
JOIN ON 조건에 쓰이는 FK 컬럼 값의 종류가 적은 컬럼 (성별, 상태 등)
ORDER BY에 자주 쓰이는 컬럼 INSERT/UPDATE/DELETE가 매우 잦은 컬럼
카디널리티(고유값 수)가 높은 컬럼 WHERE에 함수를 씌워 쓰는 컬럼
04

뷰(View) - 가상 테이블 완전 정복

💡 뷰(View)란?
실제 데이터는 없고 SELECT 쿼리를 저장해 놓은 가상의 테이블입니다.
자주 쓰는 복잡한 JOIN 쿼리를 뷰로 저장하면, 나중에 단순 테이블처럼 사용할 수 있습니다!
-- 뷰 생성: 회원별 주문 통계
CREATE VIEW v_user_order_summary AS
SELECT
    u.id AS user_id, u.name AS 회원명, u.grade AS 등급,
    COUNT(o.order_id) AS 주문횟수,
    COALESCE(SUM(o.amount), 0) AS 총구매액,
    MAX(o.order_date) AS 최근주문일
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.grade;

-- 뷰 사용: 일반 테이블처럼 SELECT!
SELECT * FROM v_user_order_summary WHERE 등급 = 'VIP';
SELECT * FROM v_user_order_summary WHERE 총구매액 >= 1000000;

-- 뷰 수정 / 삭제
CREATE OR REPLACE VIEW v_user_order_summary AS ...;
DROP VIEW IF EXISTS v_user_order_summary;
SHOW FULL TABLES WHERE Table_type = 'VIEW';
05

스토어드 프로시저 & 함수 완전 정복

💡 스토어드 프로시저란?
여러 SQL 문장을 하나의 블록으로 묶어 DB 서버에 저장해두고, 이름으로 호출해서 실행하는 프로그램입니다.
구분 Stored Procedure (프로시저) Stored Function (함수)
반환값 없거나 OUT 파라미터 반드시 단일 값 반환 (RETURN)
호출 방법 CALL proc_name() SELECT func_name()
-- 프로시저 생성
DELIMITER //
CREATE PROCEDURE get_users_by_grade(
    IN p_grade VARCHAR(20),
    OUT p_count INT
)
BEGIN
    SELECT id, name, email, grade FROM users WHERE grade = p_grade;
    SELECT COUNT(*) INTO p_count FROM users WHERE grade = p_grade;
    IF p_count = 0 THEN
        SELECT '해당 등급 회원이 없습니다.' AS message;
    END IF;
END //
DELIMITER ;

-- 프로시저 호출
CALL get_users_by_grade('VIP', @cnt);
SELECT @cnt AS VIP회원수;

-- 사용자 정의 함수: 등급에 따라 할인율 반환
DELIMITER //
CREATE FUNCTION get_discount_rate(p_grade VARCHAR(20))
RETURNS DECIMAL(3,2) DETERMINISTIC
BEGIN
    DECLARE v_rate DECIMAL(3,2);
    IF p_grade = 'VIP' THEN SET v_rate = 0.20;
    ELSEIF p_grade = '골드' THEN SET v_rate = 0.10;
    ELSE SET v_rate = 0.05;
    END IF;
    RETURN v_rate;
END //
DELIMITER ;

-- 함수 사용
SELECT name, grade, get_discount_rate(grade) AS 할인율,
       amount * (1 - get_discount_rate(grade)) AS 할인후금액
FROM users u JOIN orders o ON u.id = o.user_id;
06

트리거(Trigger) - 자동 실행 이벤트

💡 트리거란?
특정 이벤트(INSERT, UPDATE, DELETE) 발생 시 자동으로 실행되는 SQL입니다.
데이터 변경 이력 추적, 자동 집계 갱신, 비즈니스 규칙 자동 적용에 사용합니다.
시점 이벤트 사용 가능한 값 사용 예시
BEFORE INSERT NEW 유효성 검사, 자동 값 설정
AFTER UPDATE NEW, OLD 변경 이력 저장, 집계 갱신
AFTER DELETE OLD 삭제 이력 백업
-- 트리거: users 테이블 UPDATE 시 자동으로 이력 저장
CREATE TABLE user_change_log (
    log_id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT, changed_field VARCHAR(50),
    old_value VARCHAR(200), new_value VARCHAR(200),
    changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

DELIMITER //
CREATE TRIGGER trg_users_after_update
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
    IF OLD.grade != NEW.grade THEN
        INSERT INTO user_change_log (user_id, changed_field, old_value, new_value)
        VALUES (OLD.id, 'grade', OLD.grade, NEW.grade);
    END IF;
    IF OLD.email != NEW.email THEN
        INSERT INTO user_change_log (user_id, changed_field, old_value, new_value)
        VALUES (OLD.id, 'email', OLD.email, NEW.email);
    END IF;
END //
DELIMITER ;

-- 테스트: 등급 변경하면 자동으로 로그 저장!
UPDATE users SET grade = 'VIP' WHERE id = 2;
SELECT * FROM user_change_log;
07

윈도우 함수(Window Function) - 고급 분석의 핵심

💡 윈도우 함수란?
GROUP BY처럼 집계하지만 행을 합치지 않고 각 행에 집계 결과를 보여주는 함수입니다.
순위 계산, 누적합, 이전/다음 값 비교 등 고급 데이터 분석에 필수! MySQL 8.0+, PostgreSQL 지원
함수 기능 차이점
ROW_NUMBER() 1, 2, 3, 4... (중복 없음) 동점도 다른 번호
RANK() 1, 2, 2, 4... (동점 같은 순위) 동점 다음은 건너뜀
DENSE_RANK() 1, 2, 2, 3... (동점 같은 순위) 건너뛰지 않음
LAG / LEAD 이전/다음 행 값 행 간 비교에 사용
-- 기본 문법
함수명() OVER (
    [PARTITION BY 그룹컬럼]  -- 그룹 기준
    [ORDER BY 정렬컬럼]       -- 순서 기준
)

-- 예제 1: 등급별 순위 (PARTITION BY로 그룹 내 순위!)
SELECT u.name, u.grade, o.amount,
    RANK() OVER (PARTITION BY u.grade ORDER BY o.amount DESC) AS 등급내순위
FROM users u JOIN orders o ON u.id = o.user_id;

-- 예제 2: 누적합 & 이전 주문과 비교
SELECT order_date, amount,
    SUM(amount) OVER (ORDER BY order_date) AS 누적합,
    LAG(amount, 1, 0) OVER (ORDER BY order_date) AS 전회금액,
    amount - LAG(amount, 1, 0) OVER (ORDER BY order_date) AS 증감
FROM orders WHERE user_id = 1;
08

트랜잭션 심화 & 잠금(Lock) 완전 정복

📊 격리 수준 4단계

격리 수준 Dirty Read Non-Repeatable Read Phantom Read 성능
READ UNCOMMITTED 발생 발생 발생 ⭐⭐⭐⭐⭐
READ COMMITTED 방지 ✅ 발생 발생 ⭐⭐⭐⭐
REPEATABLE READ (MySQL 기본) 방지 ✅ 방지 ✅ 발생 ⭐⭐⭐
SERIALIZABLE 방지 ✅ 방지 ✅ 방지 ✅
-- 트랜잭션 실전 패턴: 안전한 계좌 이체
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- 잠금!
UPDATE accounts SET balance = balance - 100000 WHERE id = 1;
UPDATE accounts SET balance = balance + 100000 WHERE id = 2;
INSERT INTO transfer_log (from_id, to_id, amount) VALUES (1, 2, 100000);
COMMIT; -- 성공 시 확정
-- ROLLBACK; -- 실패 시 전부 취소!
09

쿼리 성능 최적화 실전

-- ❌ 나쁜 예 1: WHERE에 함수 사용 → 인덱스 무력화!
SELECT * FROM orders WHERE YEAR(order_date) = 2024;

-- ✅ 좋은 예 1: BETWEEN으로 범위 조건 사용
SELECT product_name, amount FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31 23:59:59';

-- ❌ 나쁜 예 2: LIKE '%키워드%' 앞에 % 사용 → 인덱스 무효
SELECT * FROM users WHERE name LIKE '%홍%';

-- ✅ 좋은 예 2: 앞에 % 제거
SELECT name, email FROM users WHERE name LIKE '홍%';

-- ❌ 나쁜 예 3: 상관 서브쿼리 (N+1 문제!)
SELECT name, (SELECT SUM(amount) FROM orders WHERE user_id = u.id) AS 총구매
FROM users u;

-- ✅ 좋은 예 3: JOIN으로 변경
SELECT u.name, SUM(o.amount) AS 총구매
FROM users u LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;

📋 성능 최적화 체크리스트

체크 최적화 항목 이유
SELECT * 대신 필요한 컬럼만 지정 네트워크 부하 감소
WHERE 조건 컬럼에 인덱스 생성 Full Scan → Index Scan
WHERE에 함수 사용 금지 인덱스 무력화 방지
EXPLAIN으로 실행 계획 확인 인덱스 활용 여부 확인
N+1 문제 → JOIN으로 해결 루프 안에서 쿼리 실행 금지
10

실전 프로젝트: 쇼핑몰 DB 완성 (종합 실습)

🎯 프로젝트 목표
실제 운영 가능한 쇼핑몰 데이터베이스를 처음부터 끝까지 직접 설계하고 구현합니다.
JOIN, 서브쿼리, 인덱스, 뷰, 트리거를 모두 활용하는 종합 실습 프로젝트입니다!
-- 쇼핑몰 DB 생성
CREATE DATABASE shop_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE shop_db;

-- 카테고리 테이블
CREATE TABLE categories (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    parent_id INT DEFAULT NULL,
    FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);

-- 상품 테이블
CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    category_id INT NOT NULL,
    name VARCHAR(200) NOT NULL,
    price DECIMAL(12, 0) NOT NULL DEFAULT 0,
    stock INT NOT NULL DEFAULT 0,
    description TEXT,
    is_active TINYINT(1) DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (category_id) REFERENCES categories(id),
    INDEX idx_category (category_id),
    INDEX idx_price (price),
    FULLTEXT INDEX idx_search (name, description)
);

-- 회원 테이블
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    name VARCHAR(50) NOT NULL,
    grade ENUM('일반', '실버', '골드', 'VIP') DEFAULT '일반',
    total_purchase DECIMAL(15, 0) DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_grade (grade)
);

-- 주문 테이블
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    total_amount DECIMAL(12, 0) NOT NULL,
    status ENUM('대기', '결제완료', '배송중', '배송완료', '취소', '환불') DEFAULT '대기',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    INDEX idx_user_id (user_id),
    INDEX idx_status_date (status, created_at)
);

-- 주문 상세 테이블
CREATE TABLE order_items (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    unit_price DECIMAL(12, 0) NOT NULL,
    subtotal DECIMAL(12, 0) GENERATED ALWAYS AS (quantity * unit_price) STORED,
    FOREIGN KEY (order_id) REFERENCES orders(id),
    FOREIGN KEY (product_id) REFERENCES products(id)
);

-- 핵심 분석 쿼리: 카테고리별 월별 매출 + 순위
SELECT
    c.name AS 카테고리,
    DATE_FORMAT(o.created_at, '%Y-%m') AS 월,
    COUNT(DISTINCT o.id) AS 주문수,
    SUM(oi.subtotal) AS 매출액,
    RANK() OVER (
        PARTITION BY DATE_FORMAT(o.created_at, '%Y-%m')
        ORDER BY SUM(oi.subtotal) DESC
    ) AS 월별매출순위
FROM categories c
JOIN products p ON c.id = p.category_id
JOIN order_items oi ON p.id = oi.product_id
JOIN orders o ON oi.order_id = o.id
WHERE o.status = '배송완료'
GROUP BY c.id, c.name, DATE_FORMAT(o.created_at, '%Y-%m')
ORDER BY 월 DESC, 매출액 DESC;
11

💼 현업 면접 SQL 심화 Q&A TOP 10

Q1. INNER JOIN과 LEFT JOIN의 차이를 설명하고, 각각 언제 사용하나요?
INNER JOIN은 두 테이블에 모두 존재하는 데이터만 반환하는 교집합입니다. LEFT JOIN은 왼쪽 테이블의 모든 행을 반환하고, 오른쪽에 일치하는 데이터가 없으면 NULL로 채웁니다.

사용 시점: "주문한 회원의 정보만 필요할 때" → INNER JOIN. "모든 회원 목록 + 주문 정보도(주문 없는 회원도 포함)" → LEFT JOIN. LEFT JOIN 후 WHERE 오른쪽 컬럼 IS NULL 패턴으로 "~하지 않은 데이터"를 찾는 방법은 현업에서 매우 자주 사용됩니다.
Q2. 서브쿼리와 JOIN의 차이점과 각각 언제 사용하나요?
일반적으로 JOIN이 서브쿼리보다 성능이 좋습니다. DB 옵티마이저가 JOIN에 대해 더 많은 최적화를 수행하기 때문입니다.

서브쿼리가 유리: 단순 조건 필터링(EXISTS, IN)에만 사용할 때, 의미적으로 코드가 더 명확할 때. JOIN이 유리: 양쪽 테이블의 컬럼을 모두 사용할 때. CTE(WITH절)는 복잡한 다단계 처리를 가독성 있게 만들어 현업에서 많이 사용됩니다.
Q3. 인덱스를 언제 걸어야 하고, 오히려 성능을 저하시키는 경우는?
걸어야 하는 경우: WHERE 조건에 자주 쓰이는 컬럼, JOIN ON에 사용되는 FK 컬럼, ORDER BY에 자주 쓰이는 컬럼.

인덱스가 독이 되는 경우: INSERT/UPDATE/DELETE가 매우 잦은 테이블, 값의 종류가 적은 컬럼(성별, 상태 등), WHERE에 함수를 씌워 사용하는 경우(WHERE YEAR(date)=2024는 인덱스 무력화), 데이터가 적은 소규모 테이블.
Q4. 트랜잭션 격리 수준 4가지와 MySQL의 기본 격리 수준은?
① READ UNCOMMITTED: 커밋되지 않은 데이터도 읽음 (거의 사용 안 함) ② READ COMMITTED: 커밋된 데이터만 읽음 (Oracle/PostgreSQL 기본) ③ REPEATABLE READ: 같은 트랜잭션 내에서 같은 SELECT 결과 보장 (MySQL InnoDB 기본) ④ SERIALIZABLE: 완전한 격리, 성능 가장 낮음.
Q5. 데드락(Deadlock)이란 무엇이고 어떻게 예방하나요?
데드락은 두 트랜잭션이 서로 상대방이 점유한 자원을 기다리며 무한히 대기하는 상태입니다.

예방 방법: ① 항상 같은 순서로 테이블/행에 접근 ② 트랜잭션을 가능한 짧게 유지 ③ 불필요한 FOR UPDATE 사용 금지 ④ 적절한 인덱스로 잠금 범위 최소화. MySQL은 데드락 감지 시 한쪽 트랜잭션을 자동으로 롤백합니다.
Q6. 윈도우 함수와 GROUP BY의 차이점을 설명하세요.
GROUP BY는 여러 행을 하나로 합쳐서 집계 결과를 반환합니다 (행이 줄어듦). 윈도우 함수는 집계하지만 각 행을 유지한 채로 집계 결과를 각 행에 추가합니다 (행 수 유지).

예) "각 주문의 금액 + 전체 합계 + 현재 행까지 누적합"을 함께 보려면 GROUP BY로는 불가능하고 윈도우 함수(SUM() OVER())로 해결합니다.
Q7. 복합 인덱스의 컬럼 순서가 중요한 이유는?
복합 인덱스는 왼쪽 접두사 원칙(Leftmost Prefix Rule)을 따릅니다. INDEX(A, B, C)를 만들면 WHERE A=? 또는 WHERE A=? AND B=? 조건에는 활용되지만, WHERE B=? 또는 WHERE C=?만 있으면 인덱스를 활용 못합니다. 따라서 가장 자주 사용되고 카디널리티가 높은 컬럼을 앞에 배치해야 합니다.
Q8. EXPLAIN 결과에서 어떤 항목을 가장 먼저 확인하나요?
type 컬럼을 가장 먼저 확인합니다. type=ALL이면 Full Table Scan으로 최악입니다.

확인 우선순위: ① type (ALL → 인덱스 추가 필요) ② key (어떤 인덱스를 사용했는지) ③ rows (예상 처리 행 수) ④ Extra (Using filesort, Using temporary). EXPLAIN ANALYZE(MySQL 8.0+)로 실제 실행 시간도 확인 가능합니다.
Q9. N+1 쿼리 문제란 무엇이고 어떻게 해결하나요?
N+1 문제는 1번의 쿼리로 N개의 데이터를 가져온 뒤, 각 데이터에 대해 추가 쿼리를 N번 더 실행해서 총 N+1번의 쿼리가 발생하는 문제입니다.

예: 회원 10명 조회(1번) → 각 회원의 주문 조회(10번) = 총 11번 쿼리
해결: JOIN을 사용해 한 번의 쿼리로 해결, 또는 ORM에서는 Eager Loading 사용.
Q10. 뷰(View)를 사용하는 이유와 한계점은?
사용 이유: ① 복잡한 쿼리 재사용 ② 보안(민감한 컬럼 숨김) ③ 논리적 데이터 독립성.

한계점: ① 뷰 자체는 데이터를 저장하지 않아 매번 쿼리가 재실행됨(성능 이슈) ② 복잡한 뷰에 대한 DML 제한 ③ MySQL에서 뷰에 인덱스 생성 불가. 성능이 중요한 경우 별도 요약 테이블을 관리하는 패턴을 사용합니다.
12

🗺️ 데이터베이스 개발자 완전 학습 로드맵

단계 학습 내용 기간 상태
1단계 DB 개념 이해 - 테이블, 키, ACID, DBMS 비교 (Guide0001) 1~2주 ✅ 완료
2단계 SQL 기초 - SELECT, WHERE, INSERT, UPDATE, DELETE, GROUP BY (Guide0002) 3~4주 ✅ 완료
3단계 SQL 심화 - JOIN, 서브쿼리, 인덱스, 뷰, 프로시저, 윈도우함수, 성능최적화 (Guide0003 - 현재) 3~4주 📖 현재
4단계 DB 설계 - 정규화(1NF~3NF), ERD 설계, 쇼핑몰 DB 완성 (Guide0004) 2~3주 ⏳ 예정
5단계 NoSQL 입문 - MongoDB, Redis (Guide0005) 2주 ⏳ 예정
6단계 실전 활용 - ORM(Prisma/TypeORM), 백엔드 연동, DB 보안 (Guide0006) 2주 ⏳ 예정
7단계 실전 프로젝트 - 대용량 DB 설계 및 최적화 (Guide0007) 3~4주 ⏳ 예정

✅ 이 가이드(3단계) 완료 체크리스트

🔗 JOIN 완전 마스터
☑️ INNER, LEFT, RIGHT JOIN 구분
☑️ 3개 이상 테이블 JOIN
☑️ LEFT JOIN + IS NULL 패턴
🔍 서브쿼리 & CTE
☑️ WHERE절 서브쿼리 (IN, EXISTS)
☑️ FROM절 인라인 뷰
☑️ WITH절 CTE 작성
⚡ 인덱스 & 최적화
☑️ 인덱스 생성/관리
☑️ EXPLAIN 결과 해석
☑️ 나쁜 쿼리 패턴 인식
🏗️ 고급 SQL 객체
☑️ VIEW 생성 및 활용
☑️ Stored Procedure 작성
☑️ Trigger 구현

🔧 추천 실습 환경 & 학습 자료

도구/사이트 용도 추천도 비용
MySQL 8.0 + DBeaver 로컬 실습 환경 (현업에 가장 가까운 환경) ⭐⭐⭐⭐⭐ 무료
SQLZoo.net 인터랙티브 SQL 문제 풀이 (JOIN 파트 필수!) ⭐⭐⭐⭐⭐ 무료
LeetCode Database 실제 기업 면접 SQL 문제 (Easy→Medium→Hard) ⭐⭐⭐⭐⭐ 무료(기본)
pgexercises.com PostgreSQL 심화 문제 (JOIN, 윈도우함수 특화) ⭐⭐⭐⭐ 무료

🎉 SQL 심화 완전 정복 완료!

JOIN, 서브쿼리, 인덱스, 뷰, 스토어드 프로시저, 트리거,
윈도우 함수, 트랜잭션 심화, 쿼리 최적화까지
데이터베이스 개발자가 알아야 할 모든 것을 마스터했습니다!

✅ SELECT 완전 마스터  |  ✅ JOIN 완전 정복
✅ 인덱스 설계 능력  |  ✅ 성능 최적화 기법
✅ 고급 SQL 객체 구현  |  ✅ 현업 면접 대비

📌 다음 단계 → DatabaseDevGuide0004: DB 설계 - 정규화, ERD, 쇼핑몰 DB 완전 설계
반응형