[Oracle] 07. 인덱스 (Index)
Oracle 인덱스의 종류, 생성/관리 방법, 실행 계획 분석을 정리합니다.
인덱스 종류
| 종류 | 설명 | 용도 |
|---|---|---|
| B-Tree Index | 기본 인덱스, 균형 트리 구조 | 일반적인 검색 (기본값) |
| Unique Index | 중복 불가 인덱스 | PK, UK 제약 |
| Composite Index | 복합 인덱스 (다중 컬럼) | 다중 조건 검색 |
| Bitmap Index | 비트맵 기반 인덱스 | 카디널리티 낮은 컬럼 (성별, 상태) |
| Function-Based Index | 함수 기반 인덱스 | 함수/연산 적용 컬럼 검색 |
| Reverse Key Index | 키 값을 역순 저장 | 시퀀스 기반 INSERT 경합 방지 |
| Index-Organized Table (IOT) | 테이블 자체가 인덱스 | PK 기반 조회가 대부분인 경우 |
인덱스 생성 및 관리
생성
-- 기본 인덱스
CREATE INDEX idx_emp_name ON employees (name);
-- 유니크 인덱스
CREATE UNIQUE INDEX uk_emp_email ON employees (email);
-- 복합 인덱스
CREATE INDEX idx_emp_dept_sal ON employees (dept_id, salary);
-- 비트맵 인덱스 (DW/OLAP 환경에서 사용)
CREATE BITMAP INDEX bix_emp_dept ON employees (dept_id);
-- 함수 기반 인덱스
CREATE INDEX idx_emp_upper_name ON employees (UPPER(name));
-- 역순 키 인덱스
CREATE INDEX idx_emp_id_rev ON employees (id) REVERSE;
-- 테이블스페이스 지정
CREATE INDEX idx_emp_name ON employees (name) TABLESPACE ts_index;
조회
-- 현재 사용자의 인덱스 목록
SELECT index_name, table_name, uniqueness, status
FROM user_indexes
WHERE table_name = 'EMPLOYEES';
-- 인덱스 컬럼 정보
SELECT index_name, column_name, column_position
FROM user_ind_columns
WHERE table_name = 'EMPLOYEES'
ORDER BY index_name, column_position;
-- 인덱스 크기 확인
SELECT segment_name, bytes/1024/1024 AS size_mb
FROM user_segments
WHERE segment_type = 'INDEX'
ORDER BY bytes DESC;
관리
-- 인덱스 삭제
DROP INDEX idx_emp_name;
-- 인덱스 재구축 (단편화 해소)
ALTER INDEX idx_emp_name REBUILD;
ALTER INDEX idx_emp_name REBUILD ONLINE; -- 온라인 재구축 (서비스 중단 없이)
-- 인덱스 비활성화 / 활성화
ALTER INDEX idx_emp_name UNUSABLE;
ALTER INDEX idx_emp_name REBUILD; -- 다시 활성화
-- 인덱스 통계 갱신
ANALYZE INDEX idx_emp_name COMPUTE STATISTICS;
-- 또는
EXEC DBMS_STATS.GATHER_INDEX_STATS('DEVUSER', 'IDX_EMP_NAME');
실행 계획 (EXPLAIN PLAN)
실행 계획 확인 방법
-- 방법 1: EXPLAIN PLAN
EXPLAIN PLAN FOR
SELECT * FROM employees WHERE dept_id = 1 AND salary > 5000000;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- 방법 2: AUTOTRACE (SQL*Plus)
SET AUTOTRACE ON
SELECT * FROM employees WHERE dept_id = 1;
SET AUTOTRACE OFF
-- 방법 3: 실제 실행 통계 포함
SELECT /*+ GATHER_PLAN_STATISTICS */ *
FROM employees WHERE dept_id = 1;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST'));
실행 계획 주요 오퍼레이션
| 오퍼레이션 | 설명 | 성능 |
|---|---|---|
| TABLE ACCESS FULL | 테이블 전체 스캔 | 나쁨 (대용량 시) |
| TABLE ACCESS BY INDEX ROWID | 인덱스로 찾은 ROWID로 테이블 접근 | 좋음 |
| INDEX UNIQUE SCAN | PK/UK 인덱스로 1건 조회 | 최고 |
| INDEX RANGE SCAN | 인덱스 범위 스캔 | 좋음 |
| INDEX FULL SCAN | 인덱스 전체 스캔 | 보통 |
| INDEX FAST FULL SCAN | 인덱스 전체를 멀티블록 읽기 | 보통 |
| INDEX SKIP SCAN | 복합 인덱스 선두 컬럼 건너뜀 | 보통 |
| NESTED LOOPS | 중첩 루프 조인 | 소량 데이터에 좋음 |
| HASH JOIN | 해시 조인 | 대량 데이터에 좋음 |
| SORT MERGE JOIN | 정렬 병합 조인 | 정렬된 데이터에 좋음 |
복합 인덱스와 최좌선 원칙
INDEX idx_abc (a, b, c) 인덱스가 있을 때:
| WHERE 조건 | 인덱스 사용 |
|---|---|
WHERE a = 1 |
✅ 사용 |
WHERE a = 1 AND b = 2 |
✅ 사용 |
WHERE a = 1 AND b = 2 AND c = 3 |
✅ 전체 사용 |
WHERE b = 2 |
⚠️ INDEX SKIP SCAN 가능 |
WHERE b = 2 AND c = 3 |
⚠️ INDEX SKIP SCAN 가능 |
WHERE a = 1 AND c = 3 |
⚠️ a만 사용 |
Oracle은 INDEX SKIP SCAN을 지원하여 선두 컬럼이 없어도 인덱스를 사용할 수 있지만,
선두 컬럼의 카디널리티가 낮을 때만 효과적입니다.
인덱스가 사용되지 않는 경우
-- ❌ 컬럼에 함수 적용
SELECT * FROM employees WHERE UPPER(name) = '홍길동';
-- ✅ 함수 기반 인덱스 생성
CREATE INDEX idx_upper_name ON employees (UPPER(name));
SELECT * FROM employees WHERE UPPER(name) = '홍길동';
-- ❌ 묵시적 타입 변환
SELECT * FROM employees WHERE id = '1'; -- id가 NUMBER인데 문자열 비교
-- ✅ 올바른 타입
SELECT * FROM employees WHERE id = 1;
-- ❌ LIKE 앞쪽 와일드카드
SELECT * FROM employees WHERE name LIKE '%길동';
-- ❌ NOT, != 조건
SELECT * FROM employees WHERE dept_id != 1;
-- ❌ IS NULL (B-Tree 인덱스는 NULL 미저장)
SELECT * FROM employees WHERE dept_id IS NULL;
-- ✅ 함수 기반 인덱스로 해결
CREATE INDEX idx_dept_null ON employees (NVL(dept_id, -1));
SELECT * FROM employees WHERE NVL(dept_id, -1) = -1;
힌트 (Hint)
Oracle은 옵티마이저에게 실행 계획을 제안하는 힌트를 지원합니다.
-- 인덱스 사용 강제
SELECT /*+ INDEX(e idx_emp_dept_sal) */ *
FROM employees e
WHERE dept_id = 1;
-- Full Table Scan 강제
SELECT /*+ FULL(e) */ *
FROM employees e
WHERE dept_id = 1;
-- 조인 방식 지정
SELECT /*+ USE_NL(e d) */ e.name, d.name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;
SELECT /*+ USE_HASH(e d) */ e.name, d.name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;
-- 병렬 처리
SELECT /*+ PARALLEL(e, 4) */ * FROM employees e;
| 힌트 | 설명 |
|---|---|
INDEX(table index_name) |
특정 인덱스 사용 |
FULL(table) |
Full Table Scan |
USE_NL(t1 t2) |
Nested Loop Join |
USE_HASH(t1 t2) |
Hash Join |
LEADING(t1 t2) |
조인 순서 지정 |
PARALLEL(table, n) |
병렬 처리 |
NO_INDEX(table index) |
특정 인덱스 사용 금지 |
FIRST_ROWS(n) |
처음 n건 빠르게 반환 |
ALL_ROWS |
전체 처리량 최적화 (기본) |
힌트는 옵티마이저가 잘못된 실행 계획을 선택할 때 임시 방편으로 사용합니다.
근본적으로는 통계 갱신, 인덱스 재설계가 우선입니다.
- [Oracle] 13. 자주 발생하는 Troubleshooting
- [Oracle] 12. 성능 튜닝 (Performance Tuning)
- [Oracle] 11. 백업과 복구 (Backup & Recovery)
- [Oracle] 10. 사용자 관리와 권한 (User & Privilege)
- [Oracle] 09. PL/SQL, 뷰, 프로시저, 트리거
- [Oracle] 08. 트랜잭션과 락 (Transaction & Lock)
- [Oracle] 07. 인덱스 (Index)
- [Oracle] 06. 내장 함수 정리
- [Oracle] 05. 서브쿼리와 고급 SELECT
- [Oracle] 04. JOIN (테이블 결합)
- [Oracle] 03. CRUD 기본 (INSERT, SELECT, UPDATE, DELETE)
- [Oracle] 02. 테이블스페이스와 테이블 관리
- [Oracle] 01. Oracle DB 소개 및 설치