오라클 옵티마이저가 최적의 인덱스를 선택하지 못할 때, 힌트를 사용하여 특정 인덱스 사용을 유도하는 방법을 다룹니다.
인덱스 힌트는 옵티마이저에게 특정 인덱스를 사용하도록 지시하는 강력한 도구입니다. 하지만 남용하면 오히려 성능을 저하시킬 수 있으므로 신중하게 사용해야 합니다.
INDEX 힌트 - 특정 인덱스 사용 강제
-- 기본 INDEX 힌트 사용 SELECT/*+ INDEX(e idx_emp_dept) */* FROM employees e WHERE department_id =10;
-- 여러 테이블에 각각 인덱스 힌트 적용 SELECT/*+ INDEX(e idx_emp_dept) INDEX(d idx_dept_loc) */ e.employee_id, e.last_name, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id AND d.location_id =1700;
-- 복합 인덱스 사용 강제 SELECT/*+ INDEX(e idx_emp_dept_sal) */* FROM employees e WHERE department_id =10 AND salary >5000;
INDEX_DESC 힌트 - 인덱스 역순 스캔
-- 인덱스를 역순으로 스캔 (내림차순 정렬 효과) SELECT/*+ INDEX_DESC(e idx_emp_salary) */* FROM employees e WHERE salary >5000 ORDERBY salary DESC;
-- 최근 데이터 조회 시 유용 SELECT/*+ INDEX_DESC(o idx_order_date) */* FROM orders o WHERE order_date >= TRUNC(SYSDATE) -30 ORDERBY order_date DESC;
INDEX_FFS 힌트 - Fast Full Index Scan
-- 테이블 액세스 없이 인덱스만으로 데이터 조회 SELECT/*+ INDEX_FFS(e idx_emp_dept_sal) */ department_id, salary FROM employees e WHERE department_id ISNOTNULL;
-- COUNT 연산에 효과적 SELECT/*+ INDEX_FFS(e idx_emp_dept) */ COUNT(*) FROM employees e WHERE department_id >0;
INDEX_SS 힌트 - Index Skip Scan
-- 복합 인덱스의 선행 컬럼을 조건에 사용하지 않을 때 -- 인덱스: idx_emp_dept_sal (department_id, salary) SELECT/*+ INDEX_SS(e idx_emp_dept_sal) */* FROM employees e WHERE salary >10000; -- 선행컬럼 department_id 없이 사용
-- Skip Scan이 효율적인 경우: 선행 컬럼의 distinct value가 적을 때 SELECT/*+ INDEX_SS(e idx_gender_salary) */* FROM employees e WHERE salary >8000; -- gender 컬럼(M/F 2개값)을 skip
INDEX_COMBINE 힌트 - 여러 비트맵 인덱스 결합
-- 여러 비트맵 인덱스를 결합하여 사용 SELECT/*+ INDEX_COMBINE(e idx_bmp_dept idx_bmp_job) */* FROM employees e WHERE department_id =10 AND job_id ='SA_REP';
-- 조건이 많을 때 효과적 SELECT/*+ INDEX_COMBINE(e) */*-- 모든 비트맵 인덱스 활용 FROM employees e WHERE department_id =10 AND job_id ='SA_REP' AND manager_id =100;
INDEX_JOIN 힌트 - 인덱스 조인
-- 여러 인덱스를 조인하여 테이블 액세스 없이 데이터 조회 SELECT/*+ INDEX_JOIN(e idx_emp_dept idx_emp_salary) */ department_id, salary FROM employees e WHERE department_id =10 AND salary >5000;
NO_INDEX 힌트 - 특정 인덱스 사용 금지
-- 특정 인덱스 사용 방지 SELECT/*+ NO_INDEX(e idx_emp_dept) */* FROM employees e WHERE department_id =10;
-- 여러 인덱스 사용 방지 SELECT/*+ NO_INDEX(e idx_emp_dept idx_emp_salary) */* FROM employees e WHERE department_id =10 AND salary >5000;
FULL 힌트 - Full Table Scan 강제
-- 인덱스 대신 전체 테이블 스캔 강제 SELECT/*+ FULL(e) */* FROM employees e WHERE department_id =10;
-- 대량 데이터 조회 시 FTS가 더 효율적일 수 있음 SELECT/*+ FULL(e) */COUNT(*) FROM employees e WHERE hire_date >=DATE'2020-01-01';
인덱스 힌트 조합
-- LEADING + INDEX 힌트 조합 (조인 순서 + 인덱스 지정) SELECT/*+ LEADING(d e) INDEX(e idx_emp_dept) */ e.employee_id, e.last_name, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id AND d.location_id =1700;
-- USE_NL + INDEX 힌트 조합 (Nested Loop + 인덱스) SELECT/*+ USE_NL(e d) INDEX(e idx_emp_dept) INDEX(d pk_departments) */ e.employee_id, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id AND e.employee_id =100;
-- PARALLEL + INDEX 힌트 조합 SELECT/*+ PARALLEL(e, 4) INDEX(e idx_emp_dept) */* FROM employees e WHERE department_id =10;
인덱스 힌트 사용 시 주의사항
-- 잘못된 예: 테이블 alias와 힌트 불일치 SELECT/*+ INDEX(employees idx_emp_dept) */*-- 잘못됨 FROM employees e WHERE department_id =10;
-- 올바른 예: alias 일치 SELECT/*+ INDEX(e idx_emp_dept) */*-- 올바름 FROM employees e WHERE department_id =10;
-- 인덱스 이름 오타 확인 SELECT/*+ INDEX(e idx_emp_dpt) */*-- 힌트 무시됨 (오타) FROM employees e WHERE department_id =10;
-- 존재하는 인덱스인지 확인 SELECT index_name, table_name, column_name FROM user_ind_columns WHERE table_name ='EMPLOYEES' ORDERBY index_name, column_position;
힌트 효과 확인
-- 실행 계획에서 힌트 적용 여부 확인 EXPLAIN PLAN FOR SELECT/*+ INDEX(e idx_emp_dept) */* FROM employees e WHERE department_id =10;
-- Note 섹션에서 힌트 사용 여부 확인 -- "- dynamic statistics used: dynamic sampling (level=2)" -- "- this is an adaptive plan"
인덱스 힌트 우선순위
INDEX(table index) - 가장 많이 사용, 특정 인덱스 강제
INDEX_DESC - 역순 스캔 필요 시
INDEX_FFS - 커버링 인덱스 활용
INDEX_SS - 복합 인덱스의 후행 컬럼만 사용 시
NO_INDEX - 잘못된 인덱스 선택 방지
FULL - 대량 데이터 조회 시 FTS가 유리한 경우
실전 예제
-- 예제 1: 페이징 쿼리 최적화 SELECT/*+ INDEX_DESC(o pk_orders) */* FROM ( SELECT/*+ FIRST_ROWS(10) */ o.* FROM orders o WHERE customer_id =100 ORDERBY order_date DESC ) WHERE ROWNUM <=10;
-- 예제 2: 복합 인덱스 활용 -- 인덱스: idx_emp_composite (department_id, job_id, salary) SELECT/*+ INDEX(e idx_emp_composite) */ employee_id, last_name, salary FROM employees e WHERE department_id =10-- 인덱스 1번째 컬럼 AND job_id ='SA_REP'-- 인덱스 2번째 컬럼 AND salary >5000; -- 인덱스 3번째 컬럼
-- 예제 3: 조인 쿼리 인덱스 힌트 SELECT/*+ LEADING(d e) USE_NL(e) INDEX(e idx_emp_dept) */ e.employee_id, e.last_name, d.department_name FROM departments d, employees e WHERE d.department_id = e.department_id AND d.location_id =1700;
-- 예제 4: 함수 기반 인덱스 힌트 -- 인덱스: idx_upper_lastname ON employees(UPPER(last_name)) SELECT/*+ INDEX(e idx_upper_lastname) */* FROM employees e WHEREUPPER(last_name) ='KING';
힌트를 사용하지 말아야 할 경우
옵티마이저가 이미 최적의 실행 계획을 선택한 경우
통계 정보가 정확하고 최신인 경우
데이터 분포가 자주 변경되는 테이블
애플리케이션 이식성이 중요한 경우
힌트의 효과를 정확히 이해하지 못한 경우
권장사항: 힌트는 최후의 수단으로 사용하고, 먼저 통계 수집, 인덱스 추가, SQL 재작성 등을 시도하세요.