느린 쿼리의 원인을 실행 계획에서 찾아 개선하는 방법.
실행 계획과 쿼리 튜닝
같은 결과를 내는 쿼리라도 작성 방식에 따라 속도는 수백 배 차이가 납니다. 실행 계획은 SQL Server가 쿼리를 어떻게 처리하는지 보여주는 설계도입니다. 이걸 읽을 줄 알면 느린 쿼리의 진짜 원인을 짚어낼 수 있습니다. 강좌의 마지막인 이번 레슨에서 앞서 배운 인덱스·조건 작성법을 실전 튜닝으로 종합합니다.
학습 목표
- 실행 계획을 켜고 읽는 방법을 익힙니다.
- 인덱스 스캔과 시크의 차이를 이해합니다.
- SARGable 조건으로 인덱스를 타게 만듭니다.
- 통계의 역할과 흔한 안티패턴을 파악합니다.
실행 계획 보기
SSMS에서 실제 실행 계획을 켜고(Ctrl+M) 쿼리를 실행하거나, 텍스트로 통계를 확인합니다.
-- 읽은 페이지 수와 시간 측정
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT * FROM Orders WHERE customer_id = 5;
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
STATISTICS IO 의 logical reads(읽은 페이지 수)가 핵심 지표입니다. 같은 쿼리에서 이 값이 줄면 튜닝이 잘된 것입니다.
💡 TIP — "예상 실행 계획"은 실행 없이 추정만 보여주고, "실제 실행 계획"은 실행 후 실제 행 수까지 보여줍니다. 예상 행 수와 실제 행 수가 크게 다르면 통계가 낡았거나 조건이 비효율적이라는 강한 신호입니다.
스캔 vs 시크
| 연산 | 의미 | 보통의 평가 |
|---|---|---|
| Index Seek | 인덱스로 필요한 행만 콕 집음 | 좋음 |
| Index Scan | 인덱스 전체를 훑음 | 상황에 따라 |
| Table/Clustered Scan | 테이블 전체를 훑음 | 큰 테이블이면 나쁨 |
| Key Lookup | 비클러스터 인덱스 후 본체 재조회 | 잦으면 병목 |
소량의 행을 찾는데 큰 테이블에서 Scan 이 보인다면, 적절한 인덱스가 없거나 조건이 인덱스를 타지 못하는 경우입니다. Key Lookup 이 많으면 커버링 인덱스(INCLUDE)로 없앨 수 있습니다.
SARGable 조건으로 다시 쓰기
8강에서 다룬 원칙을 튜닝 관점에서 정리합니다. 조건 열을 가공하지 않아야 인덱스 시크가 가능합니다.
-- ❌ 스캔 유발
WHERE YEAR(order_date) = 2024
WHERE CONVERT(VARCHAR, created_at, 112) = '20240101'
WHERE ISNULL(status, 'N') = 'A'
-- ✅ 시크 가능
WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01'
WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02'
WHERE status = 'A'
통계(Statistics)
옵티마이저는 열의 값 분포를 담은 통계를 보고 계획을 세웁니다. 통계가 낡으면 행 수를 잘못 추정해 엉뚱한 계획을 고릅니다.
-- 통계 수동 갱신
UPDATE STATISTICS Orders;
-- 인덱스 재구성과 함께
ALTER INDEX ALL ON Orders REBUILD;
대부분 자동 갱신이 켜져 있지만, 대량 적재 직후에는 수동 갱신이 도움이 됩니다.
흔한 안티패턴
| 안티패턴 | 문제 | 개선 |
|---|---|---|
SELECT * | 불필요한 열·Key Lookup 유발 | 필요한 열만 명시 |
| 조건 열에 함수 | 인덱스 못 탐 | SARGable하게 변형 |
OR 남용 | 인덱스 활용 저하 | UNION ALL 분리 검토 |
| 암시적 형변환 | 타입 불일치로 스캔 | 매개변수 타입 일치 |
NOT IN (NULL 포함) | 빈 결과 | NOT EXISTS |
| 함수 안의 스칼라 함수 호출 | 행마다 실행 | 인라인 TVF/식 |
-- 암시적 형변환 예: 컬럼이 VARCHAR인데 숫자로 비교
WHERE code = 12345 -- ❌ code가 VARCHAR면 형변환→스캔
WHERE code = '12345' -- ✅ 타입 일치
⚠️ 주의 — 추측으로 인덱스를 마구 추가하지 마세요. 먼저 실행 계획과
logical reads로 병목을 확인하고, 인덱스 추가 전후를 측정해 효과를 검증하는 것이 정석입니다.
튜닝 절차 요약
- 느린 쿼리를 찾는다(
STATISTICS IO/TIME, 실제 실행 계획). - Scan·Key Lookup·예상/실제 행 차이 등 병목을 식별한다.
- 조건을 SARGable하게 고치고, 필요한 인덱스를 설계한다.
- 통계를 갱신하고, 변경 전후
logical reads를 비교한다.
요약
- 실행 계획과
STATISTICS IO(logical reads)로 병목을 측정합니다. - 시크는 필요한 행만, 스캔은 전체를 읽습니다. 큰 테이블의 스캔을 줄이세요.
- 조건 열을 가공하지 않는 SARGable 작성이 인덱스 시크의 핵심입니다.
- 통계가 낡으면 계획이 어긋나니 필요 시 갱신합니다.
SELECT *·함수 조건·암시적 형변환·NOT IN NULL같은 안티패턴을 피합니다.
연습문제
STATISTICS IO를 켜고 임의 쿼리의 logical reads를 확인해 보세요.WHERE YEAR(order_date) = 2024를 시크 가능한 조건으로 바꾸세요.- Key Lookup이 잦은 쿼리를 커버링 인덱스로 개선하는 방안을 적어 보세요.
- 암시적 형변환이 스캔을 유발하는 예와 해결책을 작성하세요.
힌트 — 2번은 날짜 범위(
>=/<), 3번은INCLUDE로 필요한 열을 인덱스에 담는 것입니다.
💡 연습문제 풀이
불러오는 중…
댓글 0
“MSSQL” 강좌에 대한 댓글입니다.