MySQL에서 쿼리 성능을 좌우하는 핵심은 단순한 인덱스 유무가 아니라 어떻게 쿼리를 작성하느냐에 달려 있다. 특히 EXPLAIN으로 확인할 수 있는 실행계획 튜닝 요소를 반영해 SQL을 설계하면, 수십만 건의 데이터에서도 퍼포먼스를 안정적으로 유지할 수 있다.
🔍 기본 중의 기본: SELECT 대상 최소화
SELECT * FROM users WHERE is_active = 1;
위와 같이 *을 사용하는 것은 불필요한 데이터 전송으로 이어질 수 있다. 필요한 컬럼만 명시하는 것이 가장 기본적이면서도 중요한 최적화 전략이다.
SELECT id, name, email FROM users WHERE is_active = 1;
이런 식으로 컬럼을 명시하면 네트워크 전송량 감소와 함께 인덱스 커버링 가능성도 높아진다.
🧠 WHERE 절 설계 전략
조건 순서는 실행계획에서 직접적인 영향을 주지 않지만, 다음과 같은 방식은 인덱스 효율을 극대화할 수 있다.
-- 비효율적인 예
SELECT * FROM orders WHERE YEAR(order_date) = 2024;
-- 효율적인 예
SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
YEAR()처럼 컬럼에 함수나 연산을 걸면 인덱스를 사용할 수 없게 된다.
⚖️ IN vs EXISTS, 언제 사용해야 할까?
- IN은 정적 데이터셋일 때 적합
- EXISTS는 상관 서브쿼리에서 성능 우위
-- IN 사용 예 (작은 리스트)
SELECT * FROM product WHERE category_id IN (1, 2, 3);
-- EXISTS 사용 예 (서브쿼리)
SELECT * FROM orders o
WHERE EXISTS (
SELECT 1 FROM users u
WHERE u.id = o.user_id AND u.status = 'ACTIVE'
);
EXPLAIN으로 실행계획을 확인하며 어느 쿼리가 더 효율적인지 판단하는 습관이 중요하다.
🧮 정렬과 LIMIT은 반드시 인덱스 고려
-- 느릴 수 있는 쿼리
SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
이런 쿼리는 Using filesort와 Using temporary를 유발할 수 있다. 이 경우 다음과 같이 정렬 순서를 포함한 인덱스를 추가하는 것이 좋다.
CREATE INDEX idx_created_at ON orders(created_at DESC);
정렬 기준이 명확하면 MySQL은 인덱스를 활용해 정렬과 탐색을 동시에 수행한다.
🧬 조인 시 반드시 고려할 점
조인(Join) 쿼리에서 성능 저하가 발생하는 경우는 대부분 키 조건 누락 또는 테이블 순서 미스 때문이다.
-- 조인 키 누락 → 풀조인
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'ACTIVE';
이 때 users.status에 인덱스가 없다면 사용자 테이블 전체 스캔이 발생한다. 필터 조건으로 사용되는 컬럼에 반드시 보조 인덱스를 생성하자.
🔄 불필요한 서브쿼리, 쿼리 리팩토링으로 제거
-- 서브쿼리 예
SELECT name FROM users WHERE id IN (SELECT user_id FROM orders);
-- 리팩토링한 예
SELECT DISTINCT u.name
FROM users u
JOIN orders o ON u.id = o.user_id;
서브쿼리 → JOIN 리팩토링은 성능뿐 아니라 가독성에도 유리하다. 특히 중첩된 서브쿼리는 가능하면 제거하고 JOIN으로 변환하는 것이 좋다.
🧰 EXPLAIN으로 실시간 확인하는 습관
최적화를 하더라도 실행계획이 실제로 어떻게 동작하는지 모르면 무의미하다.
EXPLAIN SELECT id, name FROM users WHERE status = 'ACTIVE';
이렇게 EXPLAIN으로 확인하면 type, key, rows, Extra 정보로 인해 쿼리의 병목 원인을 파악할 수 있다.
📌 성능 설계를 위한 종합 팁
전략 | 설명 |
SELECT 대상 최소화 | SELECT * 지양 |
WHERE 조건 함수 제거 | YEAR() 등 사용 지양 |
JOIN 키와 순서 점검 | 인덱스 포함 확인 |
LIMIT + ORDER BY는 인덱스로 대응 | 정렬 기준 인덱스 생성 |
가능한 서브쿼리는 JOIN으로 리팩토링 | 쿼리 흐름 단순화 |
EXPLAIN으로 검증 | 실행계획 체크 습관 |
📎 참고 공식문서
MySQL Optimizing Queries:
https://dev.mysql.com/doc/refman/8.0/en/query-optimization.html
'DB' 카테고리의 다른 글
[MySQL] 실행계획 캐시와 쿼리 플랜 재사용 이해하기 🚀 (1) | 2025.07.04 |
---|---|
[MySQL] 서브쿼리 vs JOIN 성능 비교와 실행계획으로 분석하기 (1) | 2025.07.04 |
[MySQL] JOIN이 느릴 때 실행계획으로 튜닝하는 방법 (0) | 2025.07.04 |
[MySQL] 실무 예제로 배우는 느린 쿼리 튜닝 실습 (EXPLAIN + INDEX 활용) (0) | 2025.07.04 |
[MySQL] 인덱스를 잘 써도 느린 이유? EXPLAIN으로 원인 분석하기 (1) | 2025.07.04 |
[MySQL] Extra 컬럼에 자주 뜨는 문구 해석법 (Using temporary 등) 🧐 (1) | 2025.07.04 |
[MySQL] key vs possible_keys vs rows 차이점 제대로 알기 🔍 (0) | 2025.07.04 |
[MySQL] EXPLAIN 컬럼 완전정복: type, key, rows, Extra 해석법 🔬 (0) | 2025.07.04 |