MariaDB SARGABLE 개념 · EXPLAIN · Profiling 기반 검증
해당 글은 저의 의문으로 시작해 AI를 활용하고 내용에 대한 검증을 위해 공식문서를 기준으로 찾아보고
직접 테스트하면서 내용을 요약한 진행과정 사례입니다.
들어가며
개선을 진행하면서 정보들을 알아보니 이런 내용들을 보았다.
“WHERE 절의 조건 기준 컬럼에 함수를 사용하면 인덱스를 타지 않는다.”
이 문장은 MariaDB 공식 문서에 그대로 등장하는 표현은 아니다.
하지만 검색을 하다 보면 여러 기술 글, Q&A, 블로그에서 반복적으로 등장하고,
SARGABLE(Searh ARGument ABLE) 이라는 SQL 공통 개념과도 연결된다.
그래서 자연스럽게 이런 의문이 들었다.
- 정말로 함수를 사용하면 인덱스를 전혀 사용하지 못하는 걸까?
- 아니면 인덱스를 사용하긴 하지만 비효율적인 방식으로 사용하는 걸까?
이 글은 그 의문을 개념 정리 + 공식 문서 + 실제 테스트(EXPLAIN, Profiling) 를 통해 정리한 기록이다.
SARGABLE 이란 무엇인가
SARGABLE은 다음을 의미한다.
인덱스를 검색 조건으로 활용할 수 있는 쿼리 형태
일반적으로 아래와 같은 형태는 Non-SARGable 로 분류된다.
FUNCTION(column) = constant
이유는 단순하다.
- 인덱스는 컬럼의 원래 값을 기준으로 정렬된 구조이고
- 컬럼을 함수나 표현식으로 감싸면
- 옵티마이저가 인덱스를 이용해
- “어디서부터 어디까지” 탐색해야 하는지를 계산하기 어렵기 때문이다
이 원칙은 MariaDB에만 해당하는 것이 아니라,
대부분의 관계형 DBMS에 공통으로 적용된다.
MariaDB 공식 문서에서의 근거
MariaDB 공식 문서는
“함수를 쓰면 인덱스를 못 쓴다”라고 단정적으로 말하지는 않는다.
대신 다음과 같은 전제를 여러 문서에서 공통적으로 드러낸다.
- 인덱스는 컬럼 값 그 자체 기준으로 설계된다
- 표현식이나 함수는 기본 인덱스 활용 대상이 아니다
- 이를 인덱스로 활용하려면
- Generated Column
- Virtual Column + Index
같은 명시적인 설계가 필요하다
이 원칙은 WHERE 절뿐 아니라
ORDER BY, GROUP BY, JOIN 조건 전반에 공통 적용된다.
테스트 환경
- DBMS: MariaDB
- 테이블명: views_count_test
- 전체 데이터 수: 15,346 rows
- 조건 범위에 실제 해당하는 데이터 수: 631 rows
- 인덱스:
- idx_views_count_test_created_at (created_at)
테스트 쿼리
함수 사용 쿼리
SELECT *
FROM views_count_test
WHERE DATE_FORMAT(created_at, '%Y-%m-%d %H')
BETWEEN DATE_FORMAT('2025-12-31 00:00:00', '%Y-%m-%d %H')
AND DATE_FORMAT(NOW(), '%Y-%m-%d %H');
함수 미사용 쿼리 (SARGable)
SELECT *
FROM views_count_test
WHERE created_at >= '2025-12-31 00:00:00'
AND created_at < NOW();
EXPLAIN 결과 요약
인덱스가 없을 때
- 두 쿼리 모두 type = ALL
- 함수 사용 여부와 무관하게 풀 테이블 스캔
- 비교 의미 없음
created_at 인덱스 추가 후
함수 사용 쿼리
- type: index 또는 ALL
- rows: 약 15,000
- 인덱스를 검색 조건으로 사용하지 못함
- 인덱스 전체 또는 테이블 전체를 스캔하는 구조
함수 미사용 쿼리
- type: range
- rows: 약 631
- 인덱스를 범위 조건으로 정확히 활용
- 가장 이상적인 접근 방식
체감 속도와 실제 실행 시간의 차이
DB 클라이언트 기준으로는
두 쿼리 모두 약 370~400ms로 큰 차이가 없어 보였다.
그래서 실제 실행 시간을 확인하기 위해
MariaDB Profiling을 사용했다.
SET profiling = 1;
SHOW PROFILES;
Profiling 결과
쿼리실행 시간
| 함수 사용 | 약 10 ms |
| 함수 미사용 | 약 1.3 ms |
약 7~8배 차이가 발생했다.
체감 차이가 크지 않았던 이유는 다음과 같다.
- 데이터 규모가 작음
- 대부분 메모리 캐시에서 처리
- 네트워크, 클라이언트 렌더링 비용이 실제 차이를 덮음
하지만 DB 내부 실행 비용 자체는 명확히 달랐다.
이 문제는 WHERE 절에만 국한되지 않는다
다음 형태는 모든 절에서 동일한 문제를 만든다.
FUNCTION(column) = constant
WHERE 절
- 범위 계산 불가
- index full scan 또는 table scan 가능성 증가
ORDER BY 절
- 함수 기반 정렬은 인덱스 정렬 사용 불가
- filesort 발생
GROUP BY 절
- 인덱스 기반 그룹핑 불가
- 임시 테이블 + 정렬 가능성 증가
JOIN ON 절
- 인덱스 조인 어려움
- Nested Loop + Full Scan 가능성 증가
즉,
“이걸 쓰면 인덱스를 못 탄다”가 아니라
옵티마이저 판단에 따라 언제든 비효율적인 실행 계획으로 밀릴 수 있다
가 더 정확한 표현이다.
공식적인 해결책
MariaDB에서 공식적으로 권장되는 방식은
Generated Column + Index 이다.
ALTER TABLE orders
ADD created_ymd DATE
AS (DATE(created_at)) VIRTUAL,
ADD INDEX idx_created_ymd (created_ymd);
이후에는 다음과 같은 쿼리가 가능해진다.
WHERE created_ymd = '2026-01-01'
ORDER BY created_ymd
GROUP BY created_ymd
함수 결과를 컬럼으로 승격시키는 방식이다.
최종 결론
처음의 의문은 이것이었다.
“함수를 쓰면 인덱스를 못 타는가?”
테스트와 문서를 통해 얻은 결론은 다음과 같다.
- 함수를 사용해도 인덱스를 아예 못 타는 것은 아니다
- 하지만 대부분의 경우
- SARGable 하지 않아
- 인덱스를 검색 조건으로 활용하지 못하고
- 비효율적인 실행 계획으로 밀릴 가능성이 커진다
앞으로의 쿼리 작성 기준
이번 검증을 통해 정리한 개인적인 기준은 다음과 같다.
- 가능하면 SARGable 쿼리를 작성한다
- 컬럼은 좌측에 그대로 두고
- 함수보다 범위 조건을 우선 고려한다
- 성능이 중요한 경로에서는
- Generated Column + Index를 적극 검토한다
- EXPLAIN뿐 아니라 실제 실행 시간도 함께 확인한다
한 줄 요약
컬럼에 함수를 사용하면 인덱스를 못 타는 것이 아니라,
SARGable 하지 않아 옵티마이저가 비효율적인 실행 계획을 선택할 가능성이 커진다.
댓글