2025년 8월 5일
카테고리 : DB이론
조회 : 19|3분 읽기
인덱스, 정말 쓰기만 하면 빨라질까?
인덱스, 정말 쓰기만 하면 빨라질까?
많은 개발자들이 인덱스만 잘 걸어두면 성능이 좋아질 거라고 생각합니다. 하지만 인덱스를 걸었는데도 느리거나, 오히려 성능이 더 나빠지는 경험을 한 적은 없으신가요? 이번 글에서는 인덱스가 성능을 악화시키는 원인, 그리고 오라클 힌트절을 이용한 튜닝 전략까지 정리해보겠습니다.
✅ 인덱스, 정말 빠르기만 할까요?
인덱스는 기본적으로 B-Tree(Balanced Tree) 구조를 사용합니다. 검색 속도는 평균 O(log N)으로 상당히 빠르지만, 다음과 같은 상황에서는 인덱스를 타지 않거나, 타도 느릴 수 있습니다.
📌 인덱스를 무력화하는 대표적 상황
옵티마이저는 조건절 내 컬럼 중 일부에만 인덱스가 있어도, 전체 조건의 효율이 낮다고 판단되면 인덱스를 무시하고 풀스캔을 선택할 수 있습니다.
예:
sql1SELECT * 2FROM USERS 3WHERE GENDER = 'F' -- 인덱스 있음 4 AND NICKNAME LIKE '%a%' -- 인덱스 없음 (후방 와일드카드)
- GENDER에는 인덱스가 있지만, NICKNAME은 조건이 비효율적이므로 전체 테이블을 스캔할 수 있습니다.
- 조건 순서와는 관계없이 옵티마이저는 전체 비용 기준으로 판단합니다.
💡 복합 인덱스를 만들거나 조건을 재설계하여 옵티마이저가 인덱스를 활용하기 쉬운 형태로 리팩토링할 수 있습니다.
📌 인덱스를 걸면 효과적인 상황 예시
다음은 특정 컬럼에 인덱스를 걸었을 때 성능 향상이 큰 실무 상황입니다:
| 테이블 구조 예시 | 자주 쓰이는 쿼리 조건 | 추천 인덱스 컬럼 | 효과 |
|---|---|---|---|
| TB_USER(USER_ID(PK), JOIN_DATE, GENDER, IS_ACTIVE) | WHERE IS_ACTIVE = 'Y' AND JOIN_DATE >= SYSDATE - 7 | (IS_ACTIVE, JOIN_DATE) | 최신 가입자 필터링 속도 향상, 선별률 우수 |
| TB_LOGIN_LOG(USER_ID, DEVICE, LOGIN_AT) | WHERE DEVICE = 'ANDROID' AND LOGIN_AT >= :fromDate | (DEVICE, LOGIN_AT) | 로그 테이블에 적합, 정렬+필터 모두 개선 |
| TB_ORDERS(ORDER_ID(PK), ORDER_DATE, STATUS) | WHERE STATUS IN ('PENDING', 'IN_PROGRESS') | (STATUS, ORDER_DATE) | 부정 조건 회피하고 다건 조회 속도 개선 |
💡 인덱스를 만들기 전에 꼭 생각해야 할 것:
- 해당 컬럼이 자주 필터 조건에 사용되는가?
- 정렬이나 그룹핑에 쓰이는가?
- 중복이 심하지 않은가? (선택도가 높은가?)
- 결과 레코드 수가 전체의 소수인가? (전체 조회면 인덱스 효과 없음)
🎯 옵티마이저가 인덱스를 선택하지 않는 이유
오라클 옵티마이저는 쿼리 실행 시 내부 통계 정보를 바탕으로 다양한 실행 계획 중 가장 비용이 낮다고 판단되는 경로를 선택합니다. 그러나 이 선택이 항상 우리가 원하는 대로 흘러가지는 않습니다.
📌 옵티마이저의 실행 계획 결정 흐름
- 통계가 최신이 아닐 경우 잘못된 판단을 할 수 있습니다.
- WHERE 절 조건이 비효율적이면 인덱스를 회피합니다.
- 심지어 힌트를 줘도 무시되는 경우도 있습니다 (옵티마이저 판단 우선).
🔧 힌트절로 인덱스 강제 사용하기
옵티마이저가 인덱스를 선택하지 않는 경우, 힌트절을 통해 실행 계획을 유도하거나 강제할 수 있습니다. 이는 복합 조건, 부정 연산자, 함수 사용 등으로 옵티마이저가 인덱스를 무시하는 경우 유용합니다.
📌 힌트절 흐름 구조
✅ 힌트절 사용 예시
sql1-- UPPER 함수로 인해 인덱스가 무시되는 상황에서 힌트 적용 2SELECT /*+ INDEX(u name_upper_idx) */ 3FROM USERS u 4WHERE UPPER(u.name) = 'JOHN';
💡 != 같은 부정 조건도 힌트절로 인덱스를 유도할 수 있지만, 옵티마이저가 여전히 무시할 수 있기 때문에 EXPLAIN PLAN으로 확인은 필수입니다.
🧰 주요 힌트 목록
| 힌트 | 설명 |
|---|---|
| INDEX(table index_name) | 특정 인덱스 강제 사용 |
| FULL(table) | 테이블 풀스캔 유도 |
| USE_NL(a b) | Nested Loop Join 유도 |
| LEADING(a b) | 조인 순서 지정 |
| INDEX_COMBINE(table) | 비트맵 인덱스 조합 사용 |
| INDEX_SKIP_SCAN(table) | 복합 인덱스에서 선두 컬럼 없이 접근 |
✅ 인덱스 잘 쓰는 팁
- WHERE 절의 컬럼 순서와 인덱스 컬럼 순서를 맞춘다
- 자주 쓰는 조건 + 정렬 컬럼을 복합 인덱스로 구성한다
- SELECT * 대신 필요한 컬럼만 선택한다
- 인덱스 통계 정보(ANALYZE TABLE, DBMS_STATS)를 주기적으로 갱신한다
🌐 다른 DBMS에서도 힌트가 있을까?
Oracle의 힌트절은 가장 강력하고 세밀하게 제어할 수 있지만, 다른 RDBMS들도 각자 유사한 기능을 제공합니다:
| DBMS | 힌트/강제 인덱스 문법 예시 | 지원 형태 |
|---|---|---|
| Oracle | /*+ INDEX(tbl idx) */ | 정통 힌트절 (/*+ */) |
| MySQL / MariaDB | USE INDEX (idx) | 키워드 기반 인덱스 유도 |
| SQL Server | WITH (INDEX(idx)) | 테이블 힌트 형태로 제공 |
| PostgreSQL | pg_hint_plan 확장 모듈 필요 | 기본 지원 X (비공식) |
💡 요약: Oracle만의 전유물은 아니지만, Oracle 힌트절은 가장 직관적이고 강력하게 작동합니다.
🔚 마무리하며
인덱스는 성능을 올려주는 도구이기도 하지만, 잘못 쓰면 오히려 병목이 될 수 있습니다. 그리고 옵티마이저는 “똑똑하지만 완벽하지 않기” 때문에, 때로는 개발자가 직접 힌트를 줘야 할 때도 있습니다.