DB는 인덱스를 “미리 정렬된 사전(dictionary)”처럼 관리합니다.
예를 들어 salary 컬럼에 인덱스가 걸려 있다면, DB 내부에는 이런 식의 정렬 구조가 존재하죠.
... 280 | 290 | 300 | 310 | 320 | ...
즉, “300”이라는 값을 찾을 때 DB는 사전에서 단어 찾듯 정확한 위치로 점프할 수 있습니다.
그런데 이때 단어를 꼬아버리면, DB는 사전을 더 이상 이용할 수 없게 됩니다.
SELECT * FROM employee
WHERE salary * 12 = 3600;
이렇게 작성하면 DB는 다음과 같이 생각합니다:
"salary 컬럼 값이 300인 데이터를 찾고 싶은데,
인덱스에 있는 값에 ‘* 12’를 해야 하네?
그러면 인덱스 순서를 유지할 수 없으니까 다 뒤져봐야겠다."
결과적으로 Full Table Scan이 발생합니다.
DB는 모든 행의 salary 값을 꺼내 하나하나 계산한 뒤 3600이 나오는지 비교해야 하기 때문입니다.
SELECT * FROM employee
WHERE salary = 3600 / 12;
여기서 3600 / 12는 계산 결과가 300이므로, 옵티마이저는 이렇게 판단합니다:
“salary 컬럼이 300인 값을 인덱스에서 바로 찾아볼게요!”
이 경우엔 salary 컬럼의 원본 인덱스를 그대로 활용할 수 있어서
Index Range Scan으로 빠르게 처리됩니다.
날짜 컬럼에 함수 적용하는 경우도 마찬가지입니다. 실무에서 정말 많이 보이는 예죠.
-- 인덱스 안 탐
SELECT * FROM orders
WHERE DATE_FORMAT(reg_date, '%Y-%m-%d') = '2026-02-11';
reg_date에 함수가 적용되면서 컬럼이 “가공된 값”으로 바뀌기 때문에
DB는 인덱스 정렬 순서를 활용할 수 없습니다.
이럴 땐 이렇게 수정해야 합니다 👇
-- 인덱스 잘 탐
SELECT * FROM orders
WHERE reg_date >= '2026-02-11 00:00:00'
AND reg_date <= '2026-02-11 23:59:59';
컬럼을 그대로 두고 비교 구문(right-hand side) 에서만 연산·가공을 수행해야 합니다.
이렇게 하면 인덱스가 정렬된 상태 그대로 탐색 대상 범위를 좁힐 수 있죠.
| 상황 | 인덱스 사용 여부 | 이유 |
|---|---|---|
salary * 12 = 3600 | ❌ | 컬럼에 연산 적용 |
salary = 3600 / 12 | ✅ | 상수 쪽만 계산 |
DATE_FORMAT(reg_date) | ❌ | 컬럼에 함수 적용 |
reg_date BETWEEN ... | ✅ | 컬럼 원본 그대로 사용 |
💡 정리하자면:
인덱스는 “가공되지 않은 원본 데이터”의 정렬 지도다.
WHERE 절에서 컬럼은 절대 가공하지 말고,
계산이 필요하다면 상수(혹은 우변) 쪽으로 넘겨라.
EXPLAIN으로 실행 계획을 확인해보면 type=ALL(Full Table Scan)이 뜬다면 대부분 이런 문제다. function() 호출이나 계산 로직을 WHERE 절에 넣기 전에 반드시 주의할 것.