현재 선택한 SQL 과정

SQLP 이론 학습

이론 목록으로 돌아가기

상관 서브쿼리 FILTER 실행: Starts·조기 종료·Semi/Anti Join

상관 서브쿼리가 FILTER로 반복 실행되는 구조와 입력 Key Cache가 Starts·I/O를 줄이는 조건을 이해합니다.

예상 읽기 21

핵심 요약

상관 서브쿼리는 안쪽 Query가 바깥 Row의 Column을 참조하는 서브쿼리입니다. Optimizer가 이를 Join으로 Unnesting하지 않으면, 바깥 Row를 읽으면서 서브쿼리를 평가하는 FILTER 형태로 실행될 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Outer Row 생성
→ 상관 Key를 Subquery에 전달
→ Inner Row Source 탐색
→ TRUE·FALSE·UNKNOWN 판단
→ Outer Row 반환 또는 제거

반복 비용은 단순히 Outer Table 전체 행 수가 아니라 다음 요소로 결정됩니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Outer 조건 통과 행 수
× Subquery가 실제로 시작된 횟수
× 1회 Inner 접근 비용

동일한 상관 Key가 반복되면 실행 중 결과 재사용이나 다른 Transformation으로 Subquery Starts가 Outer 행 수보다 작아질 수 있습니다. 다만 재사용의 크기와 충돌 방식은 구현과 실행 조건에 따라 달라질 수 있으므로 고정된 Cache 크기를 전제로 설계하지 않고 실제 Starts, A-Rows, Buffers로 효과를 확인합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
상관 서브쿼리의 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과 실행 통계 분석을 다룹니다.

학습 목표

이 이론을 학습한 뒤에는 다음을 설명할 수 있어야 합니다.

  1. 비상관 서브쿼리와 상관 서브쿼리의 차이를 구분한다.
  2. FILTER Operation이 Outer 행을 평가하는 구조를 이해한다.
  3. FILTER가 항상 상관 서브쿼리만 의미하지는 않는다는 점을 설명한다.
  4. Outer A-Rows, 상관 Key NDV, Subquery Starts의 관계를 분석한다.
  5. EXISTSNOT EXISTS의 조기 종료 원리를 이해한다.
  6. Inner Access Predicate와 Table Filter가 반복 비용에 미치는 영향을 찾는다.
  7. FILTER 방식과 Semi·Anti Join 방식의 비용 구조를 비교한다.
  8. Starts가 Row Source 시작 횟수이며 Cache Hit Counter와 다르다는 점을 설명한다.
  9. NULL 상관 Key와 EXISTS·NOT EXISTS 결과를 판단한다.
  10. Semi·Anti Join에서 Inner 중복이 Outer Cardinality에 미치는 영향을 설명한다.
  11. 첫 행 응답과 전체 결과 처리시간을 분리해 평가한다.

1. 비상관 서브쿼리와 상관 서브쿼리

1.1 비상관 서브쿼리

안쪽 Query가 바깥 Row를 참조하지 않습니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
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의 값을 참조합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
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에 따라 달라집니다. 논리적으로는 현재 사원의 부서번호를 전달해 부서별 평균을 계산합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
사원 1의 DEPARTMENT_ID → Subquery 평가
사원 2의 DEPARTMENT_ID → Subquery 평가
...

동일한 부서번호가 반복되면 같은 입력 Key에 대한 결과가 반복될 수 있습니다. Oracle 공식 설명상 상관 Subquery는 개념적으로 Parent Row마다 평가되지만 Optimizer는 Join 또는 다른 의미적으로 동등한 방식으로 Rewrite할 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Conceptual Evaluation
→ Parent Row마다 상관값 전달

Physical Execution
→ 반드시 Parent Row 수만큼 완전 실행된다는 보장은 없음

실제 실행에서 어느 정도 반복·재사용·변환됐는지는 Plan 통계로 확인합니다.


2. FILTER Operation의 의미

다음 존재 조건을 사용합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
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되지 않은 개념 실행계획은 다음과 같을 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
FILTER
  TABLE ACCESS ... ORDERS
  TABLE ACCESS BY INDEX ROWID CUSTOMERS
    INDEX UNIQUE SCAN CUSTOMERS_PK

ORDERS에서 행을 만들고, 그 행의 CUSTOMER_IDCUSTOMERS를 탐색해 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 방식의 기본 비용은 다음처럼 생각할 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
전체 비용
≈ Outer Row Source 생성 비용
 + 실제 Subquery 반복 작업량

실제 Subquery 반복 작업량
≈ Subquery Starts × Starts당 Inner 평균 작업량

Outer Row 수를 다시 곱하지 않습니다. Starts 자체가 실제 반복 시작 횟수를 이미 나타냅니다.

예를 들어 실행 통계가 다음과 같습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
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를 사용했습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
1회 평균 Buffer
≈ 2,850,000 ÷ 950,000
≈ 3 Blocks

한 번의 Probe가 3 Buffer로 작아 보여도 95만 번 반복되면 전체 부하는 큽니다.

A-RowsBuffers는 Plan Row Source에서 누적값으로 표시될 수 있으므로 다음 평균을 함께 계산합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
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와 재사용 효과

다음 세 값을 비교합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Outer A-Rows
상관 Key의 NDV
Subquery Starts

예를 들어 주문 100만 건이 100개 고객에 집중되어 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Outer A-Rows       = 1,000,000
CUSTOMER_ID NDV    =       100
Subquery Starts    =       120

동일 Key가 많이 반복되므로 Subquery 결과 재사용 효과가 큰 실행일 수 있습니다. 반대로 고객번호가 거의 모두 다르면 다음처럼 나타날 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
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 상관 조건을 봅니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
WHERE i.customer_id = o.customer_id

Outer o.customer_id가 NULL이면 조건은 다음처럼 평가됩니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
i.customer_id = NULL
→ TRUE가 아니라 UNKNOWN

따라서 NULL끼리도 일반 Equality로 Match하지 않습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
EXISTS
→ Equality Match Row가 없으면 FALSE

NOT EXISTS
→ Equality Match Row가 없으면 TRUE

업무에서 NULL을 같은 그룹으로 취급해야 한다면 Null-safe 조건을 명시해야 합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
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가 같지 않을 수 있는 이유

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
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는 조건을 만족하는 안쪽 행을 하나 찾으면 존재 여부가 결정됩니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
첫 Match 발견
→ TRUE 결정
→ 해당 Outer Row 반환 가능
→ 남은 Inner 후보를 계속 찾을 필요 없음

효율적인 Index가 있다면 한 번의 Probe가 매우 작을 수 있습니다. EXISTS의 SELECT 목록은 존재 판정에 영향을 주지 않습니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
EXISTS (SELECT 1 ...)
EXISTS (SELECT NULL ...)
EXISTS (SELECT expensive_column ...)

논리적으로는 조건을 만족하는 Row가 있는지만 판단합니다. 불필요한 표현식이 실제로 평가될지는 Transformation과 Optimizer에 맡기므로 일반적으로 SELECT 1처럼 의도를 명확히 표현합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
CREATE INDEX customers_status_ix
    ON customers(customer_id, status);

NOT EXISTS

NOT EXISTS는 조건을 만족하는 안쪽 행을 하나 찾으면 해당 Outer Row가 탈락합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
첫 Match 발견
→ NOT EXISTS는 FALSE
→ 해당 Outer Row 제거
→ 남은 Inner 후보 탐색 중단

Match가 없는 Outer Key에서는 Inner 탐색 범위를 끝까지 확인해야 할 수 있습니다. 따라서 존재하는 Key와 존재하지 않는 Key의 비율도 비용에 영향을 줍니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
EXISTS
Match가 빠르게 존재
→ 조기 성공

NOT EXISTS
Match가 빠르게 존재
→ 조기 실패

NOT EXISTS
끝까지 Match 없음
→ Outer Row 반환, Inner 범위 확인 비용 가능

6. Inner Access Path가 반복 비용을 결정한다

다음 Query를 봅니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
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 범위로 사용할 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Outer ORDER_ID
+ Bind PRODUCT_ID
→ 좁은 Index Probe

Index가 (ORDER_ID)만 있다면 PRODUCT_ID는 Table Filter로 평가될 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
ORDER_ID Index Probe
→ 해당 주문의 모든 Item ROWID
→ Table 방문
→ PRODUCT_ID Filter

Outer 행마다 이 과정이 반복되면 불필요한 ROWID Table Access가 커집니다.

존재 여부에 필요한 Column이 모두 Index에 있으면 Table 방문 없이 Index만으로 판정할 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
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 FilterTable 방문 전에 후보를 줄이는가?
Inner Table FilterROWID 방문 후 많은 행이 탈락하는가?
Inner Starts해당 비용이 몇 번 반복되는가?

7. FILTER와 Semi Join 비교

같은 존재 Query가 Unnesting되면 Semi Join 후보가 될 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
FILTER 방식
Outer Row마다 Subquery Probe

SEMI JOIN 방식
두 입력을 하나의 Join Graph에서 최적화
조건FILTER가 유리할 수 있는 상황Semi Join이 유리할 수 있는 상황
Outer 크기매우 작음
Inner Access단건 Index Probe대량 Scan·Hash 처리
첫 행 응답빠를 수 있음Build가 필요하면 늦을 수 있음
전체 처리반복 횟수가 적을 때반복 Probe를 집합 처리로 줄일 때
Join OrderOuter 중심으로 제한될 수 있음Subquery 쪽을 먼저 읽는 후보 가능

두 방식은 결과 의미가 같아도 비용 구조가 다릅니다.

Semi Join은 Inner에 Match Row가 여러 개 있어도 Outer Row를 한 번만 반환합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
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
CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Unnesting 불가
≠
Query가 잘못됨

Unnesting 가능
≠
항상 Join Plan이 더 빠름

실제 Query Block·Hint Report·Note와 실행 통계로 확인합니다.


8. NULL과 존재 조건

EXISTS는 SELECT 목록의 값이 아니라 조건을 만족하는 행의 존재를 검사합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
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이 될 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
NOT EXISTS
→ 상관 Predicate에 실제 Match가 없으면 TRUE
→ Inner의 unrelated NULL이 결과를 오염시키지 않음

NOT IN
→ 비교 집합에 NULL이 있으면 UNKNOWN 가능

Optimizer는 Null-aware Anti Join을 사용할 수 있지만 SQL 결과 의미 자체는 유지됩니다. 문법을 바꾸기 전에 NULL 가능성을 검증합니다.


9. 실제 실행 통계 확인

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
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
       );
SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT *
FROM TABLE(
    DBMS_XPLAN.DISPLAY_CURSOR(
        NULL,
        NULL,
        'ALLSTATS LAST +PREDICATE +ALIAS +NOTE'
    )
);

확인 순서는 다음과 같습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
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 설정이 없으면 실제 통계가 비어 있을 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
첫 행·초기 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을 읽을 수 있다

실전 적용 순서

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
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 성능을 각각 검증한다.

핵심 정리

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
상관 서브쿼리
→ 안쪽 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 처리량을 같은 통계로 비교하면 안 됩니다.