SQL 쿼리 최적화 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 NULL


7. 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, 쿼리 캐시)

  • 느린 쿼리 로그 모니터링