오늘 특정 조회 화면이 평소보다 4~5초씩 딜레이된다는 문의를 받고 오랜만에 프로시저를 열어봤습니다.
데이터가 200만 건 가까이 쌓인 주문 테이블이었는데, RegDate 컬럼에 인덱스도 정상적으로 걸려 있는데 왜 이러나 싶어 쿼리를 확인해 보니 WHERE 절이 이렇게 되어 있더군요.
| |
포맷 맞춘다고 습관적으로 날짜 컬럼을 CONVERT로 감싸놓은 상태였습니다.
SSMS에서 실제 실행 계획(Ctrl + M)을 켜고 돌려보니, 아니나 다를까 인덱스를 타는 Index Seek가 아니라 테이블 전체를 무식하게 뒤지는 Clustered Index Scan이 돌고 있었습니다.
컬럼을 건드리면 인덱스는 무용지물입니다
SQL Server 인덱스는 정렬된 책의 목차와 같습니다.
그런데 WHERE CONVERT(..., RegDate) = '...' 처럼 인덱스가 걸린 컬럼 자체를 함수로 감싸버리면(좌변 가공), DB 엔진 입장에서는 목차를 보고 바로 찾아갈 수가 없습니다. 200만 줄의 데이터를 한 줄씩 꺼내서 전부 CONVERT를 돌려본 다음에야 값이 맞는지 비교할 수 있기 때문입니다.
결국 인덱스를 기껏 만들어두고도 200만 번의 함수 연산과 풀 스캔을 때리게 됩니다.
해결: 컬럼은 그대로 두고 범위로 조회하기
해결은 간단합니다. 좌변의 RegDate 컬럼은 순수하게 그대로 두고, 우변의 날짜 값을 기준으로 범위를 걸어주면 됩니다.
| |
프로시저 안에서 변수를 넘겨받는다면 이렇게 작성합니다.
| |
이렇게 고치면 컬럼 원본이 훼손되지 않아서 DB 엔진이 인덱스 트리에서 2026-10-14 00:00:00 시작점을 즉시 찾아내고, 딱 그날 데이터 구간만 읽는 Index Seek로 돌아옵니다.
잠깐: 왜 BETWEEN 대신 ‘>= AND <‘를 쓸까?
간혹 귀찮아서 BETWEEN '2026-10-14 00:00:00' AND '2026-10-14 23:59:59'로 쓰시는 분들이 있는데, 레거시 시스템의 DATETIME 타입에서는 꽤 위험합니다.
MSSQL의 옛날 DATETIME은 밀리초 정밀도가 3.33ms 단위라서 23:59:59.999를 넣으면 내부적으로 다음 날 00:00:00으로 반올림되어 다음 날 첫 건 데이터가 섞여 들어옵니다. 반대로 안전하게 끈다고 23:59:59로 딱 끊으면 59초 100~997ms 사이에 들어온 데이터가 통째로 누락되는 정산 사고가 납니다.
그래서 실무에서는 항상 시작일 >= 조건과 다음날 < 조건의 반열린 구간으로 끊는 게 가장 탈이 없습니다.
수정 후 결과
SET STATISTICS IO ON; 켜고 확인해 봤습니다.
- 수정 전 (CONVERT 사용): 논리적 읽기 수 28,400회 / 약 2.1초 소요
- 수정 후 (범위 검색): 논리적 읽기 수 12회 / 0.01초 미만 (즉시 리턴)
사소한 습관 하나로 쿼리 속도가 200배 넘게 차이 납니다. 날짜 조건 쓸 때 좌변 컬럼에는 절대 함수 씌우지 않는 걸로.