DB

인덱스를 사용할 수 없는 경우

타우루스 2026. 2. 13. 21:54

 

데이터베이스에서 인덱스를 타지 않는(Index Suppression) 현상은 SQL 튜닝에서 가장 기본적이면서도 중요한 주제입니다.

 

핵심 원칙은 "인덱스 컬럼을 변형하면 인덱스를 사용할 수 없다"는 것입니다. 이를 '좌변 가공(Left-Side Manipulation)'이라고도 부릅니다.

 

인덱스가 적용되지 않거나 비효율적으로 작동하는 대표적인 7가지 케이스를 정리해 드립니다.


1. 인덱스 컬럼에 변형(함수, 연산)을 가한 경우 (가장 흔함)

인덱스는 원본 값을 기준으로 정렬되어 있습니다. 컬럼 값을 가공하면 정렬 순서가 깨지므로 인덱스를 탈 수 없습니다.

  • 안 좋은 예: WHERE SUBSTR(name, 1, 1) = 'K' (함수 사용)
  • 안 좋은 예: WHERE salary * 12 > 5000 (연산 사용)
  • 좋은 예: WHERE name LIKE 'K%'
  • 좋은 예: WHERE salary > 5000 / 12 (우변에서 연산 수행)

2. 내부적(묵시적) 형 변환이 일어나는 경우

컬럼의 데이터 타입과 비교하는 값의 데이터 타입이 다르면, DB가 내부적으로 컬럼을 변환하여 타입을 맞춥니다. 이는 1번과 마찬가지로 컬럼 변형에 해당합니다.

  • 상황: user_id 컬럼이 VARCHAR(문자열) 타입인데 숫자로 비교할 때
  • 안 좋은 예: WHERE user_id = 2024 (DB가 TO_NUMBER(user_id) = 2024로 변환 시도)
  • 좋은 예: WHERE user_id = '2024' (타입을 맞춰줌)

3. LIKE 검색 시 와일드카드가 앞에 있는 경우

B-Tree 인덱스는 '가나다' 순으로 정렬되어 있습니다. 앞글자부터 찾아야 하는데, 앞부분을 모르면 인덱스를 탈 수 없습니다.

  • 안 좋은 예: WHERE title LIKE '%검색어' (앞부분이 뭉개짐 → Full Scan)
  • 안 좋은 예: WHERE title LIKE '%검색어%'
  • 좋은 예: WHERE title LIKE '검색어%' (앞부분이 명확함 → Index Range Scan)

4. 부정형 비교를 사용하는 경우

인덱스는 "무엇이 어디에 있다"를 저장하는 것이지, "무엇이 아니다"를 찾기에는 적합하지 않습니다.

  • 케이스: <> (같지 않다), !=, NOT IN, NOT LIKE
  • 설명: "A가 아닌 것"을 찾으려면 결국 전체 데이터를 다 뒤져봐야(Full Scan) 하는 경우가 많습니다.

5. OR 조건을 잘못 사용하는 경우

OR로 연결된 조건 중 하나라도 인덱스가 없으면, 결국 전체를 다 찾아야 하므로 인덱스를 타지 않을 확률이 높습니다.

  • 상황: col1은 인덱스가 있고, col2는 인덱스가 없는 경우
  • 안 좋은 예: WHERE col1 = 'A' OR col2 = 'B'
  • 해결: UNION ALL을 사용하여 쿼리를 분리하거나, 두 컬럼 모두 인덱스를 생성해야 합니다.

6. NULL 비교 (IS NULL, IS NOT NULL)

  • 단일 컬럼 인덱스: 일반적으로 B-Tree 인덱스에는 NULL 값이 저장되지 않습니다(Oracle 기준). 따라서 IS NULL 조건은 인덱스를 쓰지 못할 수 있습니다.
  • 예외: IS NOT NULL도 전체 데이터의 대다수가 NULL이 아니라면 인덱스 효율이 떨어져서 Full Scan을 할 수 있습니다.

7. 선택도(Selectivity)가 나쁜 경우 (Optimizer의 판단)

인덱스 컬럼을 가공하지 않고 잘 썼더라도, 가져올 데이터가 전체 테이블의 일정 비율(보통 10~15% 이상)을 넘어가면 인덱스를 타지 않습니다.

  • 이유: 인덱스를 거쳐 테이블로 가는 것(Random Access)보다, 그냥 테이블을 통째로 읽는 것(Full Table Scan, Sequential Access)이 더 빠르다고 옵티마이저가 판단하기 때문입니다.

📝 요약 비교표

구분 인덱스 미사용 (Full Scan) ❌ 인덱스 사용 (Index Scan) ✅
연산 WHERE price + 100 > 1000 WHERE price > 900
함수 WHERE UPPER(name) = 'KIM' WHERE name = 'KIM' (입력값 제어)
형변환 WHERE str_col = 123 WHERE str_col = '123'
LIKE WHERE text LIKE '%ABC' WHERE text LIKE 'ABC%'
부정형 WHERE status != 'OPEN' WHERE status IN ('CLOSE', 'WAIT')
 

 

팁: 만약 "반드시 함수를 써야만 하는데 인덱스 속도가 필요하다"면, 해당 함수 결과값 자체를 인덱스로 만드는 FBI(Function Based Index, 함수기반 인덱스)를 고려해볼 수 있습니다.

'DB' 카테고리의 다른 글

[SQL] 이중 상관서브쿼리  (0) 2026.02.14
[조인] 조인을 상관서브쿼리로 표현  (0) 2026.02.14
[뷰] 갱신제약  (0) 2026.02.13
순환관계가 변경에 유연한 이유  (0) 2026.02.11
Trigger  (0) 2026.02.11