개요: 인라인 뷰(Inline View)는 어떻게 실행되는가?
오라클 SQL을 작성할 때 가독성을 높이고 복잡한 로직을 모듈화하기 위해 인라인 뷰(Inline View)나 가상 뷰(View)를 사용하는 것은 매우 일반적입니다.
-- 전형적인 인라인 뷰 활용 쿼리
SELECT E.EMP_ID, E.EMP_NAME, V.MAX_SAL
FROM EMPLOYEES E,
(SELECT DEPT_ID, MAX(SALARY) AS MAX_SAL
FROM EMPLOYEES
GROUP BY DEPT_ID) V
WHERE E.DEPT_ID = V.DEPT_ID
AND E.SALARY = V.MAX_SAL;
개발자는 쿼리를 작성할 때 "먼저 안쪽 인라인 뷰(V)를 실행해서 부서별 최고 연봉 집합을 만든 뒤, 바깥쪽 메인 쿼리와 조인하겠지"라고 생각하기 쉽습니다. 하지만 오라클 CBO(Cost-Based Optimizer)는 개발자의 생각대로 쿼리를 순차 실행하지 않는 경우가 훨씬 많습니다.
옵티마이저는 비용을 최소화하기 위해 인라인 뷰의 울타리를 허물고 메인 쿼리와 하나로 합치는 뷰 머징(View Merging, 뷰 병합) 최적화 기술을 적극적으로 수행합니다. View Merging의 내부 원리와 단순/복합 뷰 머징의 차이, 그리고 이를 제어하는 MERGE / NO_MERGE 힌트 튜닝 가이드를 파헤쳐 봅니다.
내부 메커니즘: View Merging의 두 가지 얼굴 (단순 vs 복합)
오라클 쿼리 변환 엔진(CBQT)이 뷰를 만났을 때 수행하는 병합 방식은 뷰 내부의 연산자 포함 여부에 따라 크게 두 가지로 나뉩니다.
1. 단순 뷰 머징 (Simple View Merging)
뷰 내부에 GROUP BY, DISTINCT, 집계 함수, 분석 함수, ROWNUM 등이 포함되지 않은 단순한 SELECT-FROM-WHERE 구조일 때 적용됩니다.
- 동작 원리: 인라인 뷰의 테이블과 메인 쿼리의 테이블이 마치 처음부터 하나의 SQL이었던 것처럼 완전히 합쳐집니다.
- 이점: 옵티마이저는 조인 순서(Driving Order)와 물리적 조인 알고리즘(NL, Hash, Sort Merge)을 제약 없이 자유롭게 선택할 수 있어 최적의 실행 계획을 수립할 수 있습니다.
-- [원본 쿼리]
SELECT *
FROM (SELECT EMP_ID, DEPT_ID FROM EMPLOYEES WHERE SALARY > 5000) V,
DEPARTMENTS D
WHERE V.DEPT_ID = D.DEPT_ID;
-- [단순 뷰 머징 후 변환된 형태]
SELECT E.EMP_ID, E.DEPT_ID, D.DEPT_NAME
FROM EMPLOYEES E, DEPARTMENTS D
WHERE E.DEPT_ID = D.DEPT_ID
AND E.SALARY > 5000;
2. 복합 뷰 머징 (Complex View Merging)
뷰 내부에 GROUP BY, DISTINCT 등의 집계 연산이나 집합 연산이 포함되어 있어 그대로는 합칠 수 없는 경우에 적용됩니다.
- 동작 원리: 집계 연산 때문에 데이터의 건수나 고유성이 달라지므로, 오라클은 쿼리 변환을 통해 집계 연산을 메인 쿼리 바깥으로 끌어내거나 서브쿼리 형태로 재구조화합니다. (내부적으로 VW_COL_RBR_ 또는 VW_SQ_ 형태의 임시 뷰 이름을 생성합니다.)
- 핵심 제약: 복합 뷰 머징이 성공하면 대량의 데이터를 미리 집계해 두는 것이 아니라 조인과 집계를 동시에 처리할 수 있어 I/O를 아낄 수 있지만, 잘못 적용되면 불필요한 연산이 늘어나 성능이 급격히 저하될 수 있습니다.
주요 원인 분석: 실무에서 View Merging이 독이 되는 경우
View Merging은 대다수의 경우에 득이 되지만, 실무 프로덕션 환경에서는 이 기능 때문에 치명적인 성능 저하가 발생하기도 합니다.
1. 머징으로 인한 조인 순서 왜곡 (Unwanted Merging)
대용량 이력 테이블과 소규모 마스터 테이블이 포함된 인라인 뷰가 메인 쿼리와 무분별하게 머징되면서, 옵티마이저가 대용량 이력 테이블을 메인 드라이빙(Driving) 테이블로 잘못 선택하여 인덱스를 타지 못하고 Full Table Scan을 유발하는 경우입니다.
2. 집계 뷰 머징 실패로 인한 중복 연산 (Complex Merging Overhead)
GROUP BY가 포함된 뷰를 메인 쿼리와 조인할 때, 복합 뷰 머징이 과도하게 일어나거나 반대로 아예 머징이 막혀 뷰 내부에서 전체 데이터를 다 집계한 뒤 조인하는 바람에 TEMP Tablespace Spill이 발생하는 경우입니다.
트러블슈팅 및 튜닝 가이드: MERGE와 NO_MERGE 힌트
실행 계획에서 인라인 뷰가 머징되었는지 여부는 VIEW 오퍼레이션의 존재 유무로 판단할 수 있습니다. 실행 계획에 VIEW 오퍼레이션이 사라졌다면 뷰 머징이 일어난 것이고, 여전히 VIEW 오퍼레이션이 남아 있다면 머징되지 않고 독립적으로 수행된 것입니다.
1. NO_MERGE 힌트: 뷰 머징 차단 및 실체화(Materialization) 유도
옵티마이저가 뷰를 무리하게 머징하여 조인 순서가 꼬이거나 불필요한 풀 스캔이 유발될 때는 NO_MERGE 힌트를 사용하여 인라인 뷰의 독립성을 보장해야 합니다.
-- 대량 뷰 머징을 차단하고 뷰 내부 집계를 먼저 수행(Materialize)하도록 제어
SELECT /*+ LEADING(D V) USE_HASH(V) NO_MERGE(V) */
D.DEPT_NAME, V.AVG_SAL
FROM DEPARTMENTS D,
(SELECT /*+ QB_NAME(V) */ DEPT_ID, AVG(SALARY) AS AVG_SAL
FROM EMPLOYEES
WHERE HIRE_DATE >= TO_DATE('20200101', 'YYYYMMDD')
GROUP BY DEPT_ID) V
WHERE D.DEPT_ID = V.DEPT_ID;
- 효과: NO_MERGE(V) 힌트가 들어가면 오라클은 인라인 뷰 V를 먼저 실행하여 결과 집합을 메모리(PGA)에 임시로 담아두거나(Materialization), 독립된 서브쿼리 집합으로 고정시킨 뒤 메인 쿼리와 조인합니다.
2. MERGE 힌트: 강제 뷰 머징 유도
만약 통계정보 왜곡이나 복잡한 쿼리 구조로 인해 오라클이 뷰 머징을 포기하고 비효율적인 FILTER 또는 독립 뷰 스캔을 수행할 때, 이를 강제로 합쳐서 최적의 조인 경로를 찾게 만들려면 MERGE 힌트를 사용합니다.
-- 단순 뷰인데 머징이 안 될 때 강제로 병합 유도
SELECT /*+ MERGE(V) */
E.EMP_ID, E.EMP_NAME, D.DEPT_NAME
FROM DEPARTMENTS D,
(SELECT EMP_ID, EMP_NAME, DEPT_ID
FROM EMPLOYEES
WHERE RETIRE_YN = 'N') V
WHERE D.DEPT_ID = V.DEPT_ID;
주의사항: 뷰 힌트를 사용할 때는 반드시 인라인 뷰 내부에 /*+ QB_NAME(뷰별칭) */ 쿼리 블록 이름을 부여하고 바깥쪽 힌트에서 이를 정확히 지칭해야 힌트 오작동을 방지할 수 있습니다.
요약
- View Merging의 정의: 인라인 뷰의 울타리를 허물고 메인 쿼리와 하나로 합쳐 최적의 조인 순서와 경로를 찾게 해주는 오라클 쿼리 변환 핵심 기술입니다.
- 단순 vs 복합 뷰 머징: 그룹바이/집계 연산 유무에 따라 단순 머징(완전 통합)과 복합 머징(쿼리 구조 재조정)으로 나뉩니다.
- 실무 튜닝 가이드:
- 옵티마이저의 무리한 병합으로 조인 순서가 깨질 때 /*+ NO_MERGE(뷰이름) */
- 병합이 누락되어 독립 뷰 스캔 및 I/O가 늘어날 때 /*+ MERGE(뷰이름) */
