상관 서브쿼리 FILTER 실행: Starts·조기 종료·Semi/Anti Join
상관 서브쿼리가 FILTER로 반복 실행되는 구조와 입력 Key Cache가 Starts·I/O를 줄이는 조건을 이해합니다.
핵심 요약
상관 서브쿼리는 안쪽 Query가 바깥 Row의 Column을 참조하는 서브쿼리입니다. Optimizer가 이를 Join으로 Unnesting하지 않으면, 바깥 Row를 읽으면서 서브쿼리를 평가하는 FILTER 형태로 실행될 수 있습니다.
Outer Row 생성
→ 상관 Key를 Subquery에 전달
→ Inner Row Source 탐색
→ TRUE·FALSE·UNKNOWN 판단
→ Outer Row 반환 또는 제거
반복 비용은 단순히 Outer Table 전체 행 수가 아니라 다음 요소로 결정됩니다.
Outer 조건 통과 행 수
× Subquery가 실제로 시작된 횟수
× 1회 Inner 접근 비용
동일한 상관 Key가 반복되면 실행 중 결과 재사용이나 다른 Transformation으로 Subquery Starts가 Outer 행 수보다 작아질 수 있습니다. 다만 재사용의 크기와 충돌 방식은 구현과 실행 조건에 따라 달라질 수 있으므로 고정된 Cache 크기를 전제로 설계하지 않고 실제 Starts, A-Rows, Buffers로 효과를 확인합니다.
상관 서브쿼리의 SQL 의미
→ Outer Row의 값에 따라 Inner 결과 결정
실제 실행 방식
→ FILTER 반복 Probe
→ Scalar 결과 재사용
→ Semi·Anti Join
→ View·Join Transformation
이 이론의 범위
SQLP
SQL 고급 활용 및 튜닝 → 서브쿼리와 조인 변환범위에서 상관 Subquery, FILTER, 반복 Probe, EXISTS·NOT EXISTS, Semi·Anti Join, Unnesting과 실행 통계 분석을 다룹니다.
학습 목표
이 이론을 학습한 뒤에는 다음을 설명할 수 있어야 합니다.
- 비상관 서브쿼리와 상관 서브쿼리의 차이를 구분한다.
FILTEROperation이 Outer 행을 평가하는 구조를 이해한다.FILTER가 항상 상관 서브쿼리만 의미하지는 않는다는 점을 설명한다.- Outer
A-Rows, 상관 Key NDV, SubqueryStarts의 관계를 분석한다. EXISTS와NOT EXISTS의 조기 종료 원리를 이해한다.- Inner Access Predicate와 Table Filter가 반복 비용에 미치는 영향을 찾는다.
- FILTER 방식과 Semi·Anti Join 방식의 비용 구조를 비교한다.
Starts가 Row Source 시작 횟수이며 Cache Hit Counter와 다르다는 점을 설명한다.- NULL 상관 Key와
EXISTS·NOT EXISTS결과를 판단한다. - Semi·Anti Join에서 Inner 중복이 Outer Cardinality에 미치는 영향을 설명한다.
- 첫 행 응답과 전체 결과 처리시간을 분리해 평가한다.
1. 비상관 서브쿼리와 상관 서브쿼리
1.1 비상관 서브쿼리
안쪽 Query가 바깥 Row를 참조하지 않습니다.
SELECT e.employee_id,
e.salary
FROM employees e
WHERE e.salary > (
SELECT AVG(salary)
FROM employees
);
평균 급여는 바깥 사원마다 다른 값이 필요하지 않습니다. Optimizer는 Subquery를 한 번 계산하거나 다른 형태로 변환할 수 있습니다.
비상관 Subquery도 실행계획에서 FILTER와 별도 Row Source로 나타날 수 있으므로 FILTER가 보인다는 사실만으로 상관 실행이라고 판단하지 않습니다.
1.2 상관 서브쿼리
안쪽 Query가 현재 바깥 Row의 값을 참조합니다.
SELECT e.employee_id,
e.department_id,
e.salary
FROM employees e
WHERE e.salary > (
SELECT AVG(x.salary)
FROM employees x
WHERE x.department_id = e.department_id
);
안쪽 평균은 e.department_id에 따라 달라집니다. 논리적으로는 현재 사원의 부서번호를 전달해 부서별 평균을 계산합니다.
사원 1의 DEPARTMENT_ID → Subquery 평가
사원 2의 DEPARTMENT_ID → Subquery 평가
...
동일한 부서번호가 반복되면 같은 입력 Key에 대한 결과가 반복될 수 있습니다. Oracle 공식 설명상 상관 Subquery는 개념적으로 Parent Row마다 평가되지만 Optimizer는 Join 또는 다른 의미적으로 동등한 방식으로 Rewrite할 수 있습니다.
Conceptual Evaluation
→ Parent Row마다 상관값 전달
Physical Execution
→ 반드시 Parent Row 수만큼 완전 실행된다는 보장은 없음
실제 실행에서 어느 정도 반복·재사용·변환됐는지는 Plan 통계로 확인합니다.
2. FILTER Operation의 의미
다음 존재 조건을 사용합니다.
SELECT o.order_id,
o.customer_id
FROM orders o
WHERE EXISTS (
SELECT 1
FROM customers c
WHERE c.customer_id = o.customer_id
AND c.status = 'ACTIVE'
);
Unnesting되지 않은 개념 실행계획은 다음과 같을 수 있습니다.
FILTER
TABLE ACCESS ... ORDERS
TABLE ACCESS BY INDEX ROWID CUSTOMERS
INDEX UNIQUE SCAN CUSTOMERS_PK
ORDERS에서 행을 만들고, 그 행의 CUSTOMER_ID로 CUSTOMERS를 탐색해 ACTIVE 고객인지 확인합니다.
FILTER는 일반적인 조건 평가 Operation이므로 실행계획에 보인다고 항상 상관 서브쿼리 또는 Cache가 존재한다고 단정하지 않습니다.
FILTER는 다음 상황에도 나타날 수 있습니다.
- 일반 Boolean Predicate 평가
- 비상관 Subquery 조건
- Null-aware Anti Join 보조 Predicate
- 실행 중 한 번만 평가되는 조건
다음을 함께 확인합니다.
FILTER아래에 별도 Subquery Row Source가 있는가?- Predicate Information에 어떤 조건이 남아 있는가?
- Subquery Root의
Starts는 얼마인가? - Outer Row Source의
A-Rows와 어떤 관계인가? - Query Block Name·Object Alias로 어느 Subquery인지 확인되는가?
- Note에 Unnesting·Transformation 관련 정보가 있는가?
Starts는 해당 Operation이 시작된 횟수입니다. 내부 Cache Hit·Miss 횟수나 PL/SQL 함수 호출 횟수와 동일한 지표라고 단정하지 않습니다.
3. 반복 비용 계산
FILTER 방식의 기본 비용은 다음처럼 생각할 수 있습니다.
전체 비용
≈ Outer Row Source 생성 비용
+ 실제 Subquery 반복 작업량
실제 Subquery 반복 작업량
≈ Subquery Starts × Starts당 Inner 평균 작업량
Outer Row 수를 다시 곱하지 않습니다. Starts 자체가 실제 반복 시작 횟수를 이미 나타냅니다.
예를 들어 실행 통계가 다음과 같습니다.
ORDERS TABLE ACCESS A-Rows = 1,000,000
CUSTOMER Subquery Root Starts = 950,000
CUSTOMER Index Scan Buffers = 2,850,000
Subquery가 거의 Outer 행 수만큼 시작됐고, Index Scan에서 약 285만 Buffer를 사용했습니다.
1회 평균 Buffer
≈ 2,850,000 ÷ 950,000
≈ 3 Blocks
한 번의 Probe가 3 Buffer로 작아 보여도 95만 번 반복되면 전체 부하는 큽니다.
A-Rows와 Buffers는 Plan Row Source에서 누적값으로 표시될 수 있으므로 다음 평균을 함께 계산합니다.
Starts당 Inner 반환 Row
= Inner A-Rows ÷ Subquery Starts
Starts당 Inner Buffer
= Inner Buffers ÷ Subquery Starts
다만 A-Rows=0이어도 Index Block을 여러 개 읽을 수 있으므로 반환 Row 수만으로 Probe 비용을 판단하지 않습니다.
반대로 Outer 1,000행, Subquery Starts 20, Inner Buffers 60이라면 반복 Key 재사용이나 다른 최적화로 실제 Subquery 작업량이 매우 작을 수 있습니다.
4. 상관 Key NDV와 재사용 효과
다음 세 값을 비교합니다.
Outer A-Rows
상관 Key의 NDV
Subquery Starts
예를 들어 주문 100만 건이 100개 고객에 집중되어 있습니다.
Outer A-Rows = 1,000,000
CUSTOMER_ID NDV = 100
Subquery Starts = 120
동일 Key가 많이 반복되므로 Subquery 결과 재사용 효과가 큰 실행일 수 있습니다. 반대로 고객번호가 거의 모두 다르면 다음처럼 나타날 수 있습니다.
Outer A-Rows = 1,000,000
CUSTOMER_ID NDV = 990,000
Subquery Starts = 980,000
이 경우 Cache나 재사용만으로 반복 비용을 줄이기 어렵습니다.
다음 점을 함께 고려합니다.
- Cache 크기와 Hash 충돌은 고정된 SQL 설계 규칙으로 가정하지 않음
- 동일 Key가 반복되어도 Optimizer Transformation에 따라 Plan 구조가 달라질 수 있음
- Outer 입력 순서를 바꾸는 추가 Sort는 재사용을 늘릴 수 있어도 Sort 비용을 새로 만듦
- 일부 행만 Fetch한 통계와 전체 Fetch 통계를 구분
4.1 상관 Key가 NULL인 경우
다음 Equality 상관 조건을 봅니다.
WHERE i.customer_id = o.customer_id
Outer o.customer_id가 NULL이면 조건은 다음처럼 평가됩니다.
i.customer_id = NULL
→ TRUE가 아니라 UNKNOWN
따라서 NULL끼리도 일반 Equality로 Match하지 않습니다.
EXISTS
→ Equality Match Row가 없으면 FALSE
NOT EXISTS
→ Equality Match Row가 없으면 TRUE
업무에서 NULL을 같은 그룹으로 취급해야 한다면 Null-safe 조건을 명시해야 합니다.
WHERE i.customer_id = o.customer_id
OR (i.customer_id IS NULL AND o.customer_id IS NULL)
Null-safe 조건은 Index Access와 Unnesting 가능성에 영향을 줄 수 있으므로 정확성과 Plan을 함께 확인합니다.
4.2 NDV와 Starts가 같지 않을 수 있는 이유
NDV
→ 입력에서 관찰한 서로 다른 상관 Key 수
Starts
→ 실행계획 Operation이 실제 시작된 횟수
둘은 다음 이유로 다를 수 있습니다.
- 일부 Outer Row가 앞선 Predicate에서 제거됨
- 동일 Key 재사용·Cache 충돌
- NULL·데이터 분포
- Subquery Unnesting·Transformation
- 부분 Fetch
- Parallel Execution과 Row Source 분배
따라서 Starts = NDV를 기대값으로 고정하지 않습니다.
5. EXISTS와 NOT EXISTS의 조기 종료
EXISTS
EXISTS는 조건을 만족하는 안쪽 행을 하나 찾으면 존재 여부가 결정됩니다.
첫 Match 발견
→ TRUE 결정
→ 해당 Outer Row 반환 가능
→ 남은 Inner 후보를 계속 찾을 필요 없음
효율적인 Index가 있다면 한 번의 Probe가 매우 작을 수 있습니다. EXISTS의 SELECT 목록은 존재 판정에 영향을 주지 않습니다.
EXISTS (SELECT 1 ...)
EXISTS (SELECT NULL ...)
EXISTS (SELECT expensive_column ...)
논리적으로는 조건을 만족하는 Row가 있는지만 판단합니다. 불필요한 표현식이 실제로 평가될지는 Transformation과 Optimizer에 맡기므로 일반적으로 SELECT 1처럼 의도를 명확히 표현합니다.
CREATE INDEX customers_status_ix
ON customers(customer_id, status);
NOT EXISTS
NOT EXISTS는 조건을 만족하는 안쪽 행을 하나 찾으면 해당 Outer Row가 탈락합니다.
첫 Match 발견
→ NOT EXISTS는 FALSE
→ 해당 Outer Row 제거
→ 남은 Inner 후보 탐색 중단
Match가 없는 Outer Key에서는 Inner 탐색 범위를 끝까지 확인해야 할 수 있습니다. 따라서 존재하는 Key와 존재하지 않는 Key의 비율도 비용에 영향을 줍니다.
EXISTS
Match가 빠르게 존재
→ 조기 성공
NOT EXISTS
Match가 빠르게 존재
→ 조기 실패
NOT EXISTS
끝까지 Match 없음
→ Outer Row 반환, Inner 범위 확인 비용 가능
6. Inner Access Path가 반복 비용을 결정한다
다음 Query를 봅니다.
SELECT o.order_id
FROM orders o
WHERE EXISTS (
SELECT 1
FROM order_items i
WHERE i.order_id = o.order_id
AND i.product_id = :product_id
);
Index가 (ORDER_ID, PRODUCT_ID)라면 두 조건을 함께 Access 범위로 사용할 수 있습니다.
Outer ORDER_ID
+ Bind PRODUCT_ID
→ 좁은 Index Probe
Index가 (ORDER_ID)만 있다면 PRODUCT_ID는 Table Filter로 평가될 수 있습니다.
ORDER_ID Index Probe
→ 해당 주문의 모든 Item ROWID
→ Table 방문
→ PRODUCT_ID Filter
Outer 행마다 이 과정이 반복되면 불필요한 ROWID Table Access가 커집니다.
존재 여부에 필요한 Column이 모두 Index에 있으면 Table 방문 없이 Index만으로 판정할 수 있습니다.
Index (ORDER_ID, PRODUCT_ID)
→ 두 Equality를 Access
→ 첫 Index Entry Match
→ EXISTS TRUE
→ Table Access 불필요 가능
반대로 STATUS, DELETE_YN 등 추가 조건이 Index에 없으면 Table Filter와 반복 Random Access가 남을 수 있습니다. 다음을 구분합니다.
| 단계 | 확인 항목 |
|---|---|
| Inner Index Access | 상관 Key와 추가 조건이 access에 들어갔는가? |
| Inner Index Filter | Table 방문 전에 후보를 줄이는가? |
| Inner Table Filter | ROWID 방문 후 많은 행이 탈락하는가? |
Inner Starts | 해당 비용이 몇 번 반복되는가? |
7. FILTER와 Semi Join 비교
같은 존재 Query가 Unnesting되면 Semi Join 후보가 될 수 있습니다.
FILTER 방식
Outer Row마다 Subquery Probe
SEMI JOIN 방식
두 입력을 하나의 Join Graph에서 최적화
| 조건 | FILTER가 유리할 수 있는 상황 | Semi Join이 유리할 수 있는 상황 |
|---|---|---|
| Outer 크기 | 매우 작음 | 큼 |
| Inner Access | 단건 Index Probe | 대량 Scan·Hash 처리 |
| 첫 행 응답 | 빠를 수 있음 | Build가 필요하면 늦을 수 있음 |
| 전체 처리 | 반복 횟수가 적을 때 | 반복 Probe를 집합 처리로 줄일 때 |
| Join Order | Outer 중심으로 제한될 수 있음 | Subquery 쪽을 먼저 읽는 후보 가능 |
두 방식은 결과 의미가 같아도 비용 구조가 다릅니다.
Semi Join은 Inner에 Match Row가 여러 개 있어도 Outer Row를 한 번만 반환합니다.
Outer 주문 1건
Inner Item Match 10건
→ SEMI JOIN 결과 Outer 주문 1건
일반 Inner Join은 별도 DISTINCT가 없으면 Outer Row가 Match 수만큼 증식할 수 있으므로 EXISTS를 단순 Join으로 수동 변경할 때 결과 Cardinality를 검증합니다.
Anti Join도 Match가 하나라도 발견되면 Outer Row를 제거하고 Inner 중복 수만큼 결과를 늘리지 않습니다.
NO_UNNEST와 기본 Plan을 제한적으로 비교해 FILTER 비용을 관찰할 수 있지만, Hint는 결과 의미를 정의하지 않으며 최종 판단은 Hint 없는 정상 Plan과 실제 업무 조건을 기준으로 합니다.
7.1 Subquery Unnesting의 가능성과 제한
Subquery Unnesting은 Nested Subquery를 Join Graph에 병합해 Access Path, Join Order, Join Method를 함께 최적화하게 합니다.
Oracle은 의미적으로 동등한 결과가 보장될 때만 Transformation을 수행합니다. 다음 구조는 Unnesting을 제한하거나 복잡하게 만들 수 있습니다.
ROWNUM- Set Operator
- Nested Aggregate
- Hierarchical Query
- Immediate Parent가 아닌 Query Block과의 Correlation
- Aggregate·
GROUP BY가 있는 Correlated Subquery - 비결정적 함수·부작용
- 복잡한 OR Branch
Unnesting 불가
≠
Query가 잘못됨
Unnesting 가능
≠
항상 Join Plan이 더 빠름
실제 Query Block·Hint Report·Note와 실행 통계로 확인합니다.
8. NULL과 존재 조건
EXISTS는 SELECT 목록의 값이 아니라 조건을 만족하는 행의 존재를 검사합니다.
WHERE EXISTS (
SELECT NULL
FROM customers c
WHERE c.customer_id = o.customer_id
)
SELECT NULL이어도 조건을 만족하는 행이 있으면 EXISTS는 TRUE입니다.
반면 IN, NOT IN은 값 비교와 3값 논리의 영향을 받습니다. 특히 NOT IN은 Subquery 결과에 NULL이 하나라도 있거나 Outer 값이 NULL이면 조건 전체가 UNKNOWN이 될 수 있습니다.
NOT EXISTS
→ 상관 Predicate에 실제 Match가 없으면 TRUE
→ Inner의 unrelated NULL이 결과를 오염시키지 않음
NOT IN
→ 비교 집합에 NULL이 있으면 UNKNOWN 가능
Optimizer는 Null-aware Anti Join을 사용할 수 있지만 SQL 결과 의미 자체는 유지됩니다. 문법을 바꾸기 전에 NULL 가능성을 검증합니다.
9. 실제 실행 통계 확인
SELECT /*+ GATHER_PLAN_STATISTICS */
o.order_id
FROM orders o
WHERE EXISTS (
SELECT 1
FROM order_items i
WHERE i.order_id = o.order_id
AND i.product_id = :product_id
);
SELECT *
FROM TABLE(
DBMS_XPLAN.DISPLAY_CURSOR(
NULL,
NULL,
'ALLSTATS LAST +PREDICATE +ALIAS +NOTE'
)
);
확인 순서는 다음과 같습니다.
1. 실제 Plan이 FILTER인지 SEMI JOIN인지 확인
2. Outer A-Rows 확인
3. Subquery Root Starts 확인
4. Inner A-Rows ÷ Starts 계산
5. Inner Buffers ÷ Starts 계산
6. Access와 Filter Predicate 위치 확인
7. 전체 Fetch 여부 확인
실행이 첫 20행만 Fetch된 상태라면 Outer와 Subquery Operation도 전체 결과를 처리하지 않았을 수 있습니다. 부분범위 응답과 전체 처리량을 각각 측정합니다.
ALLSTATS LAST는 Plan Statistics가 수집된 마지막 실행의 I/O·Memory 통계를 표시합니다. GATHER_PLAN_STATISTICS Hint 또는 적절한 Statistics 설정이 없으면 실제 통계가 비어 있을 수 있습니다.
첫 행·초기 Fetch
→ 화면 응답·Interactive 목표
전체 Fetch
→ Batch·보고서·전체 처리량 목표
10. 혼동하기 쉬운 판단
| 혼동하기 쉬운 판단 | 정확한 기준 |
|---|---|
| 상관 서브쿼리는 항상 Outer 행 수만큼 완전히 실행된다 | 재사용·조기 종료·Transformation에 따라 실제 Starts와 작업량이 달라진다 |
실행계획의 FILTER는 모두 상관 서브쿼리다 | 일반 Predicate 평가에도 사용되므로 하위 Row Source와 Predicate를 함께 본다 |
| 동일 Key가 반복되면 Subquery는 정확히 NDV만큼 실행된다 | Cache 충돌과 Plan 구조가 있으므로 실제 Starts로 확인한다 |
| 한 번의 Index Probe가 싸면 FILTER 전체도 싸다 | 작은 비용도 수십만 번 반복되면 큰 총량이 된다 |
EXISTS 안의 SELECT 값이 NULL이면 FALSE다 | SELECT 값이 아니라 조건을 만족하는 행의 존재를 검사한다 |
| Semi Join은 FILTER보다 항상 빠르다 | Outer 크기, Index Probe 비용, 첫 행·전체 처리 목표에 따라 달라진다 |
| Starts는 Cache Miss 횟수다 | Row Source 시작 횟수이며 Cache Hit Counter가 아니다 |
| FILTER 비용에 Outer Row 수와 Starts를 모두 곱한다 | Starts에 실제 반복 횟수가 이미 반영된다 |
| Inner Match가 10건이면 Semi Join도 Outer를 10건 반환한다 | 존재 판정이므로 Outer Row는 한 번만 반환 |
| NULL 상관 Key끼리는 Equality로 Match한다 | NULL = NULL은 UNKNOWN이므로 명시적 Null-safe 조건 필요 |
| Unnesting Hint가 있으면 항상 Join으로 변환된다 | 의미적·구조적 제한과 Hint 적용 여부를 확인 |
| A-Rows가 0이면 Inner Probe 비용도 0이다 | 반환 Row가 없어도 Index·Table Block을 읽을 수 있다 |
실전 적용 순서
1. 상관 Column과 Outer 결과 행 수를 확인한다.
2. 상관 Key NDV와 반복 분포를 확인한다.
3. 실제 Plan이 FILTER인지 Join인지 확인한다.
4. Subquery Starts와 Starts당 Inner A-Rows·Buffers를 계산한다.
5. Starts가 Cache Counter가 아니라 Operation 시작 횟수임을 확인한다.
6. Inner Access·Index Filter·Table Filter와 Index-only 가능성을 분리한다.
7. NULL 상관 Key와 Null-safe Equality의 업무 의미를 검증한다.
8. Match·No-Match 비율과 조기 종료 효과를 확인한다.
9. Unnesting 제한과 Semi·Anti Join 대안의 결과 Cardinality를 비교한다.
10. 첫 행 응답과 전체 Fetch 성능을 각각 검증한다.
핵심 정리
상관 서브쿼리
→ 안쪽 Query가 바깥 Row의 값을 참조
FILTER 실행
→ Outer Row마다 Subquery 조건 평가 가능
반복 비용
→ Subquery Starts × Inner 1회 평균 비용
재사용 효과
→ Outer Key 반복도와 실제 Starts로 확인
튜닝
→ Outer 행 감소, Inner Access 개선, Unnesting·Semi Join 비교
개념 확인 문제
문제를 누르면 바로 아래에서 정답과 해설을 확인할 수 있습니다.
01비상관 서브쿼리와 상관 서브쿼리의 차이는 무엇인가?
비상관 Subquery는 Parent Row Column을 참조하지 않고, 상관 Subquery는 현재 Parent Row의 값을 Inner 조건에 사용합니다.
02상관 서브쿼리가 Unnesting되지 않았을 때 나타날 수 있는 대표 Operation은 무엇인가?
Unnesting되지 않은 상관 조건은 실행계획에서 FILTER 아래 별도 Subquery Row Source로 나타날 수 있습니다.
03실행계획에 FILTER가 보일 때 상관 서브쿼리인지 확인하려면 무엇을 함께 봐야 하는가?
FILTER 하위 Row Source, Predicate Information, Query Block·Alias, Subquery Root Starts와 Outer A-Rows를 함께 확인합니다. FILTER 자체는 일반 조건 평가에도 사용됩니다.
04FILTER 방식의 반복 비용을 계산할 때 가장 중요한 세 가지 수치는 무엇인가?
Outer 조건 통과 A-Rows, Subquery Starts, Inner 누적 A-Rows·Buffers와 Starts당 평균 비용을 확인합니다. 반복 비용에서 Outer Row 수와 Starts를 다시 중복 곱하지 않습니다.
05Outer A-Rows가 100만이고 Subquery Starts가 100이라면 어떤 가능성을 검토할 수 있는가?
Outer A-Rows 100만, Starts 100이면 동일 Key 재사용, 앞선 Filter, 부분 Fetch 또는 Unnesting·Transformation으로 실제 Subquery 시작이 줄었을 가능성을 검토합니다.
06동일 상관 Key의 NDV와 Subquery Starts가 반드시 같지 않은 이유는 무엇인가?
NDV는 입력의 서로 다른 Key 수이고 Starts는 Operation 시작 횟수입니다. Cache 충돌·NULL·조기 종료·Transformation·부분 Fetch 때문에 일치하지 않을 수 있습니다.
07EXISTS가 Inner Match를 하나 찾은 뒤 탐색을 중단할 수 있는 이유는 무엇인가?
EXISTS는 Match Row 하나가 있으면 TRUE가 확정되므로 Inner 탐색을 중단할 수 있습니다. Semi Join도 첫 Match에서 중단하고 Outer Row를 한 번만 반환합니다.
08Inner Index가 상관 Key만 포함하고 추가 조건은 Table Filter로 남을 때 어떤 비용이 커질 수 있는가?
상관 Key만 Index Access에 있고 추가 조건이 Table Filter라면 후보 ROWID마다 Table Block을 반복 방문한 뒤 탈락시키는 Random Access 비용이 커질 수 있습니다.
09FILTER 방식과 Semi Join 방식의 가장 큰 구조적 차이는 무엇인가?
FILTER는 Outer Row를 기준으로 Subquery를 반복 평가하고, Semi·Anti Join은 Subquery Table을 Join Graph에 포함해 Join Order·Method를 함께 최적화합니다.
10실행 통계를 비교할 때 전체 Fetch 여부를 확인해야 하는 이유는 무엇인가?
부분 Fetch에서는 Outer·Inner Operation도 전체 Row를 처리하지 않았을 수 있으므로 첫 행 응답과 전체 Batch 처리량을 같은 통계로 비교하면 안 됩니다.