[SQL] 쿼리 성능 최적화 방법

khj·2026년 1월 1일

SQL

목록 보기
3/6
post-thumbnail

DB 성능 이슈의 대부분은 쿼리 작성 방식에서 시작된다.
특히 MSSQL 환경에서는 같은 결과를 반환하더라도
쿼리 작성 방법에 따라 성능 차이가 크게 발생한다.


쿼리 성능의 핵심 요소

쿼리 성능은 주로 아래 요소에 의해 결정된다.

  • 인덱스 사용 여부
  • JOIN 방식
  • WHERE 조건 작성 방식
  • 불필요한 컬럼 조회 여부
  • 실행 계획(Execution Plan)

👉 옵티마이저가 인덱스를 제대로 타게 만드는 것이 핵심이다.


1. SELECT * 사용 지양

❌ 잘못된 예

SELECT *
FROM Orders;

문제점

  • 필요 없는 컬럼까지 모두 조회
  • I/O 증가
  • 인덱스 커버링 불가

⭕ 개선된 예

SELECT order_id, member_id, price
FROM Orders;

📌 필요한 컬럼만 조회하는 습관이 기본 최적화


2. WHERE 조건에서 인덱스 컬럼 가공 금지

❌ 인덱스를 못 타는 쿼리

SELECT *
FROM Orders
WHERE YEAR(order_date) = 2024;

➡️ 컬럼에 함수 적용 → 인덱스 사용 불가

⭕ 인덱스를 타는 쿼리

SELECT *
FROM Orders
WHERE order_date >= '2024-01-01'
  AND order_date <  '2025-01-01';

📌 WHERE 절에서는 컬럼을 가공하지 말고 값 쪽을 가공


3. LIKE 사용 시 주의사항

❌ 인덱스 미사용

SELECT *
FROM Member
WHERE name LIKE '%kim%';

➡️ 앞에 %가 오면 인덱스 사용 불가

⭕ 인덱스 사용 가능

SELECT *
FROM Member
WHERE name LIKE 'kim%';

📌 부분 검색이 많다면 FULLTEXT INDEX 고려


4. JOIN 컬럼에 인덱스 생성

JOIN 성능은 ON 조건 컬럼의 인덱스 유무에 크게 좌우된다.

❌ 인덱스 없음

SELECT *
FROM Member m
JOIN Orders o
  ON m.member_id = o.member_id;

⭕ Orders.member_id 인덱스 생성

CREATE INDEX idx_orders_member_id
ON Orders(member_id);

📌 JOIN되는 테이블의 FK 컬럼은 거의 필수 인덱스


5. LEFT JOIN + WHERE 조건 주의

❌ LEFT JOIN이 INNER JOIN처럼 동작

SELECT *
FROM Member m
LEFT JOIN Orders o
  ON m.member_id = o.member_id
WHERE o.price >= 10000;

➡️ Orders 없는 Member 제거됨

➡️ 옵티마이저가 INNER JOIN으로 처리

⭕ ON 절에 조건 명시

SELECT *
FROM Member m
LEFT JOIN Orders o
  ON m.member_id = o.member_id
 AND o.price >= 10000;

📌 결과 정확성 + 성능 모두 중요


6. EXISTS vs JOIN

JOIN 방식

SELECT DISTINCT m.member_id
FROM Member m
JOIN Orders o
  ON m.member_id = o.member_id;

EXISTS 방식 (대량 데이터에서 유리)

SELECT m.member_id
FROM Member m
WHERE EXISTS (
  SELECT 1
  FROM Orders o
  WHERE o.member_id = m.member_id
);

📌 존재 여부 확인 → EXISTS

📌 데이터 조회 → JOIN


7. 불필요한 DISTINCT 사용 금지

❌ 무분별한 DISTINCT

SELECT DISTINCT member_id
FROM Orders;

➡️ 정렬 + 중복 제거 비용 발생

📌 중복이 왜 발생하는지 먼저 구조 확인

📌 JOIN 구조 개선이 우선


8. 서브쿼리보다 JOIN 우선 고려

❌ 상관 서브쿼리

SELECT *
FROM Member m
WHERE m.member_id IN (
  SELECT o.member_id
  FROM Orders o
);

⭕ JOIN 또는 EXISTS

SELECT DISTINCT m.*
FROM Member m
JOIN Orders o
  ON m.member_id = o.member_id;

또는

SELECT *
FROM Member m
WHERE EXISTS (
  SELECT 1
  FROM Orders o
  WHERE o.member_id = m.member_id
);

📌 MSSQL 옵티마이저는 JOIN에 최적화되어 있음


9. 실행 계획(Execution Plan) 확인 습관

SET STATISTICS PROFILE ON;

또는 SSMS에서 실제 실행 계획 보기(Ctrl + M)

확인 포인트

  • Index Seek vs Index Scan
  • Table Scan 발생 여부
  • 예상 행 수 vs 실제 행 수 차이

📌 Scan이 많다면 인덱스 설계부터 의심


10. 인덱스는 많을수록 좋은 게 아니다

문제점

  • INSERT / UPDATE / DELETE 성능 저하
  • 인덱스 유지 비용 증가

원칙

  • WHERE / JOIN / ORDER BY에 사용되는 컬럼 위주
  • 복합 인덱스는 선두 컬럼 중요
CREATE INDEX idx_orders_member_date
ON Orders(member_id, order_date);

정리

  • 쿼리 성능 최적화 핵심 요약
  • SELECT * 사용하지 말 것
  • WHERE 절에서 컬럼 가공 금지
  • JOIN 컬럼에 인덱스 필수
  • LEFT JOIN 조건 위치 주의
  • EXISTS는 존재 여부 확인에 최적
  • 실행 계획 확인은 필수 습관
profile
Spring, Django 개발 블로그

0개의 댓글