Contents
see ListSQL 쿼리 최적화 10가지 팁
데이터베이스 성능은 애플리케이션 전체 성능에 큰 영향을 미칩니다. 실무에서 바로 적용할 수 있는 SQL 쿼리 최적화 기법을 알아봅니다.
1. SELECT * 피하기
-- 나쁜 예
SELECT * FROM users WHERE status = 1;
-- 좋은 예 (필요한 컬럼만)
SELECT user_id, name, email FROM users WHERE status = 1;
-- 이유: 불필요한 I/O, 네트워크 트래픽 감소
-- 커버링 인덱스 활용 가능2. 인덱스 활용
-- WHERE, JOIN, ORDER BY에 사용되는 컬럼에 인덱스
-- 인덱스 생성
CREATE INDEX idx_users_status ON users(status);
CREATE INDEX idx_orders_user_date ON orders(user_id, order_date);
-- 복합 인덱스는 왼쪽부터 사용
-- idx(a, b, c) → WHERE a, WHERE a AND b (O)
-- idx(a, b, c) → WHERE b (X)3. 함수 사용 피하기
-- 나쁜 예 (인덱스 사용 불가)
SELECT * FROM orders
WHERE YEAR(order_date) = 2024;
SELECT * FROM users
WHERE UPPER(email) = "TEST@TEST.COM";
-- 좋은 예
SELECT * FROM orders
WHERE order_date >= "2024-01-01"
AND order_date < "2025-01-01";
SELECT * FROM users
WHERE email = "test@test.com"; -- 대소문자 무시 컬럼 설정4. LIKE 최적화
-- 나쁜 예 (Full Scan)
SELECT * FROM products WHERE name LIKE "%신발%";
-- 좋은 예 (인덱스 사용 가능)
SELECT * FROM products WHERE name LIKE "나이키%";
-- 전문 검색이 필요하면
CREATE FULLTEXT INDEX idx_name ON products(name);
SELECT * FROM products WHERE MATCH(name) AGAINST("신발");5. 서브쿼리 대신 JOIN
-- 나쁜 예 (서브쿼리)
SELECT * FROM orders
WHERE user_id IN (
SELECT user_id FROM users WHERE status = 1
);
-- 좋은 예 (JOIN)
SELECT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.user_id
WHERE u.status = 1;
-- EXISTS 활용 (대용량)
SELECT * FROM orders o
WHERE EXISTS (
SELECT 1 FROM users u
WHERE u.user_id = o.user_id AND u.status = 1
);6. 적절한 데이터 타입
-- 나쁜 예
status VARCHAR(10) -- "active", "inactive"
flag VARCHAR(5) -- "true", "false"
-- 좋은 예
status TINYINT -- 1, 0
flag BOOLEAN -- true, false
-- 날짜는 문자열 아닌 DATE/DATETIME
created_at DATETIME NOT NULL7. LIMIT 활용
-- 페이징
SELECT * FROM products
ORDER BY created_at DESC
LIMIT 20 OFFSET 0;
-- 존재 여부만 확인
SELECT 1 FROM users WHERE email = ? LIMIT 1;
-- 대용량 페이징 최적화
SELECT * FROM products
WHERE id > 1000 -- 커서 기반
ORDER BY id
LIMIT 20;8. OR 대신 UNION/IN
-- 나쁜 예
SELECT * FROM products
WHERE category_id = 1 OR category_id = 2 OR category_id = 3;
-- 좋은 예 (IN)
SELECT * FROM products
WHERE category_id IN (1, 2, 3);
-- 다른 테이블이면 UNION
SELECT * FROM products WHERE category_id = 1
UNION ALL
SELECT * FROM products WHERE status = 1;9. 집계 함수 최적화
-- COUNT 최적화
-- 전체 수만 필요하면
SELECT COUNT(*) FROM users; -- 인덱스 사용
-- 조건부 COUNT
SELECT COUNT(1) FROM users WHERE status = 1;
-- 대용량은 근사값 사용
SHOW TABLE STATUS LIKE "users"; -- Rows 확인
-- GROUP BY 인덱스
CREATE INDEX idx_orders_status ON orders(status);
SELECT status, COUNT(*) FROM orders GROUP BY status;10. 실행 계획 분석
-- MySQL
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
EXPLAIN ANALYZE SELECT ...;
-- 확인 항목
-- type: ALL(풀스캔) → ref, range, const 개선
-- key: 사용된 인덱스
-- rows: 예상 스캔 행 수 (적을수록 좋음)
-- Extra: Using filesort, Using temporary 제거
-- PostgreSQL
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 1;
-- Oracle
EXPLAIN PLAN FOR SELECT ...;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);추가 팁
- 정기적으로 통계 정보 갱신 (ANALYZE TABLE)
- 불필요한 정렬 제거 (DISTINCT, ORDER BY)
- 배치 처리로 대량 작업 분할
- 캐싱 활용 (Redis, 쿼리 캐시)
- 느린 쿼리 로그 모니터링