439. 힌트(Hint) 구문을 통한 옵티마이저 제어
⚠️ 이 문서는 데이터베이스 최고의 천재인 '옵티마이저'가 가끔 통계 정보의 오류나 복잡한 쿼리 때문에 멍청한 실행 계획(풀 스캔 등)을 세워버릴 때, 개발자가 "내가 너보다 이 데이터는 더 잘 아니까, 묻지 말고 무조건 이 인덱스 타!"라고 강제로 지시를 내리는 '힌트' 구문을 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: SQL 쿼리문 안에 주석(
/*+ ... */) 형태로 숨겨서 작성하며, CBO(비용 기반 옵티마이저)의 판단을 무시하고 개발자가 원하는 실행 경로(Access Path)나 조인 방식을 강제하는 명령어다.- 가치: 아무리 쿼리를 튜닝해도 옵티마이저가 말을 안 들을 때, 최후의 수단으로 사용하여 10초 걸리던 쿼리를 0.01초 만에 끝나게 만들 수 있는 강력한 무기다.
- 주의점: 데이터가 늘어나고 환경이 바뀌면 예전에 걸어둔 힌트가 오히려 독이 되어 시스템을 마비시킬 수 있다. 힌트를 썼다면 반드시 정기적인 실행 계획 모니터링이 동반되어야 한다.
Ⅰ. 개요: 똑똑한 내비게이션 길들이기 (Context & Necessity)
"나이 컬럼에 인덱스를 걸어놨으니까 당연히 인덱스 타겠지?"
개발자는 기대하며 SELECT * FROM Users WHERE age = 20 쿼리를 날렸다.
그런데 10초가 걸렸다. 실행 계획(Explain)을 까보니 **'풀 스캔(Full Scan)'**을 타고 있다.
이유는 다양하다. 통계 정보가 어제 업데이트 안 됐거나, 옵티마이저가 계산을 잘못했기 때문이다. 이때 튜닝의 고수는 쿼리를 이렇게 바꾼다.
SELECT /*+ INDEX(Users idx_age) */ * FROM Users WHERE age = 20;
옵티마이저는 이 주석(힌트)을 발견하는 순간 자기 고집을 꺾고, 무조건 idx_age라는 인덱스를 타서 데이터를 가져온다. 속도는 0.01초로 줄어든다.
📢 섹션 요약 비유: 옵티마이저가 **'카카오내비'**라면, 힌트는 조수석에 앉은 **'택시 기사님(현지인)'**입니다. 내비가 "직진하세요"라고 해도, 기사님이 "여기 퇴근 시간엔 무조건 골목길로 빠져야 해!"라고 지시하면(힌트), 내비를 무시하고 골목길(인덱스)로 들어가는 것이 훨씬 빠를 때가 있습니다.
Ⅱ. 실무에서 가장 많이 쓰는 3대 힌트 ★
데이터베이스 벤더(Oracle, MySQL 등)마다 문법이 약간 다르지만, 보통 /*+ 힌트명 */ 형태로 쓴다.
1. 액세스 경로 힌트 (어떻게 읽어올까?)
/*+ INDEX(테이블명 인덱스명) */: 제발 풀 스캔하지 말고 이 인덱스 좀 타라!/*+ FULL(테이블명) */: 반대로, 이 테이블은 너무 작으니까 쓸데없이 인덱스 타지 말고 그냥 풀 스캔해라!/*+ INDEX_DESC(테이블명 인덱스명) */: 인덱스를 거꾸로 타라! (최신순 게시판 페이징을 구현할 때ORDER BY를 생략하게 해주는 엄청난 치트키다)
2. 조인 순서 힌트 (누구부터 합칠까?)
/*+ ORDERED */: 내가FROM A, B, C라고 적은 순서대로 조인해! 네 맘대로 섞지 마!/*+ LEADING(B A C) */: 내가 쿼리를 어떻게 짰든 상관없이 B $\rightarrow$ A $\rightarrow$ C 순서로 조인해!
3. 조인 방식 힌트 (어떻게 합칠까?)
/*+ USE_NL(A B) */: 두 테이블 합칠 때 무조건 '중첩 루프 조인(431번 문서)' 써!/*+ USE_HASH(A B) */: 이건 데이터가 너무 많으니까 무조건 '해시 조인(433번 문서)' 써서 메모리에서 합쳐!
Ⅲ. 힌트의 치명적인 양날의 검
"와! 힌트 엄청 좋네요? 앞으로 쿼리 짤 때마다 무조건 인덱스 힌트 달아놔야겠어요." 절대 안 된다. 주니어 개발자들이 가장 많이 내는 대형 사고다.
- 상황: 1년 전,
status컬럼에 인덱스 힌트를 걸어서 배포했다. 그땐 데이터가 1만 건이라 엄청 빨랐다. - 현재: 데이터가 1억 건으로 늘어났고, 그중 9,900만 건이 똑같은
status='완료'다. - 대참사: 이런 상황에서는 당연히 '풀 스캔'을 타는 게 100배 빠르다. 하지만 1년 전에 걸어둔 힌트 때문에 옵티마이저는 울며 겨자 먹기로 9,900만 번의 디스크 점프(Key Lookup)를 뛰게 되고, 결국 DB 서버가 터져버린다.
힌트는 **"코드에 박힌 영구적인 시한폭탄"**과 같다. 데이터의 분포(통계)는 매일매일 변하는데, 힌트는 변하지 않기 때문이다.
┌──────────────────────────────────────────────────────────────┐
│ 옵티마이저의 판단과 힌트(Hint)의 우선순위 시각화 │
├──────────────────────────────────────────────────────────────┤
│ │
│ [ 👨💻 쿼리 실행 ] │
│ │
│ ┌──────────────────────┐ │
│ │ 🧠 옵티마이저 (CBO) │ │
│ │ 1. 통계정보 확인 │ │
│ │ 2. 비용 계산 │ │
│ │ 3. "음, 풀 스캔이 낫겠군"│ │
│ └─────────┬────────────┘ │
│ │ │
│ ▼ (힌트 발견!) │
│ ┌──────────────────────┐ "헉! 사장님(개발자)이 무조건 │
│ │ /*+ INDEX(A idx) */ │ ──▶ 인덱스 타라고 명령하셨네!" │
│ └──────────────────────┘ (자기 계산을 폐기하고 힌트 따름) │
│ │
│ ★ 특징: 힌트 문법이 틀렸거나, 존재하지 않는 인덱스 이름을 적으면 │
│ 에러를 내지 않고 그냥 무시(주석 취소)한 뒤 자기 맘대로 실행한다. │
└──────────────────────────────────────────────────────────────┘
Ⅳ. 결론
"명장은 연장을 탓하지 않고, 고수는 힌트를 남발하지 않는다." 현대의 CBO(비용 기반 옵티마이저)는 상상을 초월할 정도로 똑똑하다. 힌트를 떡칠해서 쿼리를 최적화하려는 시도는 대부분 옵티마이저의 성능을 갉아먹는 결과로 이어진다. 쿼리가 느리다면 가장 먼저 해야 할 일은 힌트를 박는 것이 아니라, "통계 정보가 최신화되어 있는가?(420번 문서)"를 점검하고 "인덱스 컬럼의 순서(427번 문서)나 조인 쿼리의 구조 자체에 모순이 없는지"를 리팩토링하는 것이다. 힌트는 그 모든 정상적인 방법을 다 써봤는데도 해결되지 않을 때 꺼내 드는 마지막 비수(Dagger)여야 한다.
📌 관련 개념 맵
- 전제 조건: 옵티마이저 CBO (420번 문서), 실행 계획 (421번 문서)
- 제어 대상: 테이블 스캔 방식 (428, 429번), 조인 방식 (431, 432, 433번)
- 문법 특징:
/*+로 시작해서*/로 닫는다.+를 빼먹으면 그냥 무의미한 주석이 된다. - 대안 튜닝 기법: SQL Rewrite (쿼리 자체를 예쁘게 다시 짜기), 통계 정보 갱신(
ANALYZE)
👶 어린이를 위한 3줄 비유 설명
- 옵티마이저는 길을 찾아주는 '카카오내비'예요. 막히는 길, 빠른 길을 알아서 다 계산해 주죠.
- 힌트(Hint)는 아빠가 내비게이션한테 "야! 계산하지 말고 무조건 국도로 가!"라고 강제로 명령을 내리는 거예요.
- 아빠가 길을 잘 알 때는 엄청 빨리 갈 수 있지만, 만약 10년 전 기억으로 명령을 내린 거라면 꽉 막힌 길에 갇혀서 꼼짝도 못 하게 될 수 있답니다!