DB

[MySQL] 효율적인 쿼리 작성법: 실행계획 튜닝을 고려한 SQL 설계 전략 🏗️

인생아 2025. 7. 4. 09:18
반응형

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

반응형