SHIN
Oracle Hint는 SQL 안에 넣는 특수 주석으로, 옵티마이저가 실행계획을 만들 때 특정 방향을 고려하도록 지시하는 문법입니다. 예를 들어 “이 인덱스를 써라”, “풀 테이블 스캔을 해라”, “이 테이블을 먼저 조인해라”, “Nested Loop 조인을 써라” 같은 지시를 줄 수 있습니다. Oracle 공식 문서는 힌트를 “SQL 문 안의 주석 형태 지시문”으로 설명하며, 옵티마이저 모드, 쿼리 변환, 접근 경로, 조인 순서, 조인 방식 등에 영향을 줄 수 있다고 설명합니다
SELECT /*+ 힌트명(대상) */ 컬럼
FROM 테이블
WHERE 조건;SELECT /*+ INDEX(e EMP_EMP_ID_PK) */
e.employee_id, e.last_name
FROM employees e
WHERE e.employee_id = 100;힌트 주석은 SQL 블록의 첫 키워드 바로 뒤에 와야 하고, /+ ... / 형태에서 +는 / 바로 뒤에 붙어야 합니다. 여러 힌트는 하나의 힌트 주석 안에 공백으로 구분해 넣습니다. 예를 들어 SELECT /+ FULL(e) PARALLEL(e 4) */ ... 형태입니다.
주의할 점은 테이블에 별칭을 썼다면 힌트에도 테이블명이 아니라 별칭을 써야 한다는 것입니다. 예를 들어 FROM employees e라면 FULL(employees)가 아니라 FULL(e)를 써야 합니다. Oracle 공식 문서도 별칭이 있으면 힌트에서 별칭을 사용해야 한다고 설명합니다.
-- employees 테이블의 별칭 e에 대해 EMP_DEPARTMENT_IX 인덱스를 사용하도록 유도
SELECT /*+ INDEX(e EMP_DEPARTMENT_IX) */
e.employee_id, e.department_id, e.last_name
FROM employees e
WHERE e.department_id = 50;
-- department_id 조건의 선택도가 높고, 인덱스 접근이 유리하다고 판단될 때
SELECT /*+ INDEX(e EMP_DEPARTMENT_IX) */
*
FROM employees e
WHERE e.department_id = 10;
--employees 테이블을 인덱스가 아니라 Full Table Scan으로 읽도록 유도
--조건에 맞는 데이터가 너무 많아서 인덱스보다 전체 스캔이 더 빠를 때
SELECT /*+ FULL(e) */
e.employee_id, e.last_name
FROM employees e
WHERE e.last_name LIKE 'Kim%';SELECT /*+ FULL(o) */
COUNT(*)
FROM orders o
WHERE o.order_date >= DATE '2025-01-01';
--EMP_DEPARTMENT_IX 인덱스를 사용하지 말라고 유도
--특정 인덱스를 타면 오히려 느려지는 경우, 또는 잘못된 실행계획을 피하고 싶을 때 사용합니다.
SELECT /*+ NO_INDEX(e EMP_DEPARTMENT_IX) */
e.employee_id, e.department_id
FROM employees e
WHERE e.department_id = 50;
--departments d를 먼저 읽고, 그 다음 employees e를 조인하도록 유도
--LEADING은 여러 테이블 중 어떤 테이블부터 조인할지 지정할 때 자주 사용합니다
SELECT /*+ LEADING(d e) */
e.employee_id, e.last_name, d.department_name
FROM departments d
JOIN employees e
ON e.department_id = d.department_id
WHERE d.department_id = 10;
--FROM 절에 적힌 순서대로 departments → employees 순서로 조인
--LEADING은 힌트 안에서 조인 순서를 명시하는 방식이고, ORDERED는 FROM 절에 적힌 순서를 따르게 하는 방식입니다.
SELECT /*+ ORDERED */
e.employee_id, d.department_name
FROM departments d,
employees e
WHERE e.department_id = d.department_id
AND d.department_id = 10;
--d를 먼저 읽고, e를 Nested Loop 방식으로 조인
--실무적으로는 소량의 기준 데이터로 다른 테이블을 인덱스 조회할 때 유리합니다.
SELECT /*+ LEADING(d e) USE_NL(e) */
e.employee_id, e.last_name, d.department_name
FROM departments d
JOIN employees e
ON e.department_id = d.department_id
WHERE d.department_id = 10;SELECT /*+ LEADING(c o) USE_NL(o) INDEX(o ORDERS_CUST_ID_IX) */
c.customer_name, o.order_id, o.order_date
FROM customers c
JOIN orders o
ON o.customer_id = c.customer_id
WHERE c.customer_id = 100;
--employees와 departments를 Hash Join 방식으로 조인하도록 유도
--대량 데이터 조인에서는 Nested Loop보다 Hash Join이 유리한 경우가 많습니다.
SELECT /*+ USE_HASH(e d) */
e.employee_id, d.department_name
FROM employees e
JOIN departments d
ON e.department_id = d.department_id;SELECT /*+ USE_HASH(o i) */
o.order_id, i.item_id
FROM orders o
JOIN order_items i
ON i.order_id = o.order_id
WHERE o.order_date >= DATE '2025-01-01';--Sort Merge Join을 사용하도록 유도
--양쪽 데이터가 이미 정렬되어 있거나, 정렬 후 병합이 유리한 경우 사용할 수 있습니다.
SELECT /*+ USE_MERGE(e d) */
e.employee_id, d.department_name
FROM employees e
JOIN departments d
ON e.department_id = d.department_id;
--전체 결과를 처리하는 총 비용을 최소화하는 방향으로 최적화
--배치, 리포트, 대량 조회성 SQL에 어울립니다.
SELECT /*+ ALL_ROWS */
*
FROM sales
WHERE sales_date >= DATE '2025-01-01';
--전체 결과보다 처음 10건을 빠르게 가져오는 방향으로 최적화
--페이징 조회, 검색 화면, 목록 화면에서 자주 사용합니다.
SELECT /*+ FIRST_ROWS(10) */
employee_id, last_name, salary
FROM employees
WHERE department_id = 50
ORDER BY salary DESC;SELECT *
FROM (
SELECT /*+ FIRST_ROWS(20) */
p.product_id, p.product_name, p.price
FROM products p
WHERE p.category_id = 10
ORDER BY p.price DESC
)
WHERE ROWNUM <= 20;
--orders 테이블을 병렬도 4로 읽도록 유도
--대용량 테이블 집계, 배치성 쿼리에서 사용할 수 있습니다.
SELECT /*+ PARALLEL(o 4) */
COUNT(*)
FROM orders o
WHERE o.order_date >= DATE '2025-01-01';INSERT /*+ APPEND PARALLEL(t 4) */
INTO target_table t
SELECT /*+ PARALLEL(s 4) */
*
FROM source_table s;
--일반 Insert 경로가 아니라 Direct Path Insert를 유도
--대량 적재 시 자주 사용합니다. 다만 Undo, Redo, Lock, 병렬 DML, 인덱스 유지 비용 등을 함께 고려해야 합니다.
INSERT /*+ APPEND */
INTO sales_backup
SELECT *
FROM sales
WHERE sales_date < DATE '2024-01-01';
힌트는 “반드시 무조건 적용된다”기보다는, 옵티마이저가 가능한 조건에서 실행계획 선택에 반영하는 지시입니다. Oracle 문서는 문법 오류, 잘못된 객체명, 충돌하는 힌트, 쿼리 변환으로 인해 대상이 사라지는 경우 등에서는 힌트가 무시될 수 있고, 무시되어도 일반적으로 에러를 내지 않는다고 설명합니다.
예를 들어 아래는 서로 충돌할 수 있습니다.
SELECT /*+ FULL(e) INDEX(e EMP_DEPARTMENT_IX) */
*
FROM employees e
WHERE e.department_id = 10;FULL(e)는 Full Table Scan을 유도하고, INDEX(e ...)는 인덱스 스캔을 유도하므로 서로 방향이 다릅니다. 이런 식의 충돌 힌트는 의도와 다르게 무시될 수 있습니다.
EXPLAIN PLAN FOR
SELECT /*+ INDEX(e EMP_EMP_ID_PK) */
COUNT(*)
FROM employees e
WHERE e.employee_id = 100;
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'ALL'));Oracle 19c 이후에는 힌트 사용 리포트를 통해 어떤 힌트가 사용됐고 어떤 힌트가 무시됐는지 확인하기 쉬워졌습니다. DBMS_XPLAN.DISPLAY, DISPLAY_CURSOR 등에서 HINT_REPORT 포맷을 지정하면 힌트 리포트를 볼 수 있고, ALL은 사용/미사용 힌트를 모두 보여주며 TYPICAL은 최종 계획에서 사용되지 않은 힌트를 중심으로 보여줍니다.
--실행된 SQL 기준으로 확인
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST +HINT_REPORT'));
SELECT /*+ LEADING(c o) USE_NL(o) INDEX(o ORDERS_CUST_ID_IX) */
c.customer_id,
c.customer_name,
o.order_id,
o.order_date
FROM customers c
JOIN orders o
ON o.customer_id = c.customer_id
WHERE c.customer_id = 100;이런 SQL은 customers에서 1명 또는 소수 고객만 조회하고, 해당 고객의 주문을 인덱스로 찾는 구조일 때 적합합니다.
SELECT /*+ USE_HASH(o i) FULL(o) FULL(i) */
o.order_id,
i.product_id,
i.quantity
FROM orders o
JOIN order_items i
ON i.order_id = o.order_id
WHERE o.order_date >= DATE '2025-01-01';SELECT *
FROM (
SELECT /*+ FIRST_ROWS(20) INDEX(p PRODUCTS_CATEGORY_PRICE_IX) */
p.product_id,
p.product_name,
p.price
FROM products p
WHERE p.category_id = 10
ORDER BY p.price DESC
)
WHERE ROWNUM <= 20;INSERT /*+ APPEND PARALLEL(sb 4) */
INTO sales_backup sb
SELECT /*+ PARALLEL(s 4) FULL(s) */
*
FROM sales s
WHERE s.sales_date < DATE '2024-01-01';힌트는 “옵티마이저보다 내가 더 잘 안다”는 전제에서 쓰는 도구입니다. 그래서 먼저 통계정보, 인덱스 설계, SQL 조건식, 조인 조건, 파티션 프루닝 등을 점검하는 것이 좋습니다. Oracle 공식 문서도 데이터와 DB 환경 변화로 힌트가 낡거나 부정적인 영향을 줄 수 있으므로, 힌트는 테스트 용도로 유용하지만 장기적인 실행계획 관리는 SQL Tuning Advisor, SQL Plan Management, SQL Performance Analyzer 같은 도구를 권장한다고 설명합니다.
핵심만 정리하면, Oracle Hint는 성능 튜닝에 강력하지만 남용하면 SQL이 환경 변화에 취약해집니다. 특히 INDEX, FULL, LEADING, USE_NL, USE_HASH, FIRST_ROWS, PARALLEL 정도가 실무에서 가장 자주 쓰입니다.