포스트

Scalar Subquery Filter Push Down 으로 풀어내기

Scalar Subquery Filter Push Down 으로 풀어내기

인라인 뷰 안에 scalar subquery 가 있고 그 결과 컬럼을 인라인 뷰 바깥에서 filter 조건으로 사용하면, 옵티마이저는 View Merging 으로 인라인 뷰를 메인쿼리에 통합한 뒤 FPD (Filter Push Down) 로 outer 컬럼을 새 subquery 안으로 밀어 넣어 scalar subquery 를 일반 subquery filter 로 변환합니다.

핵심 정리

  1. 변환 트리거: scalar subquery 의 결과 컬럼이 인라인 뷰 바깥에서 조건절 로 사용되어야 합니다. SELECT list 에서만 참조되면 변환되지 않습니다.
  2. 변환 단계: (1) View Merging 으로 인라인 뷰가 메인쿼리에 통합 → (2) FPD 가 outer 컬럼을 변환된 subquery 안으로 push down.
  3. 결과: scalar subquery 가 사라지고 일반 subquery (correlated subquery) filter 형태로 plan 이 풀립니다. 가독성을 위해 인라인 뷰로 감싼 표기가 옵티마이저 입장에서는 같은 결과로 정리됩니다.

시나리오

employee 의 IT 직군 (job_id = 'IT_PROG') 사원 중, 소속 부서가 위치 정보 (location_id > 0) 를 가진 사원만 조회하는 쿼리입니다. 인덱스는 다음과 같습니다.

테이블인덱스컬럼
employeeEMP_XB_IXjob_id
departmentDEPT_ID_PK1department_id (PK)

department.location_id 는 PK 검색 후 가져오는 컬럼이라 인덱스 lookup 후 1 회 access. scalar subquery 가 일반 subquery 로 변환되면 이 lookup 이 EXISTS 형태의 correlated subquery 로 풀립니다.


1) 원본 — Scalar Subquery + 인라인 뷰

1
2
3
4
5
6
7
8
SELECT a.employee_id, a.first_name, a.last_name, a.email
  FROM (SELECT e.employee_id, e.first_name, e.last_name, e.email,
               (SELECT location_id
                  FROM department d
                 WHERE d.department_id = e.department_id) AS location_id
          FROM employee e
         WHERE e.job_id = 'IT_PROG') a
 WHERE a.location_id > 0;

흐름상 “직원 정보를 모은 다음 location_id 가 양수인 행만” 이 자연스럽지만, 옵티마이저는 이를 그대로 두지 않고 view merging + FPD 변환으로 풀어버립니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-------------------------------------------------------------------------------------
| Id | Operation                     | Name        | E-Rows | E-Bytes | Cost (%CPU) |
-------------------------------------------------------------------------------------
|* 1 | FILTER                        |             |        |         |             |
|  2 |  TABLE ACCESS BY INDEX ROWID  | EMPLOYEE    |      5 |     195 |       2 (0) |
|* 3 |   INDEX RANGE SCAN            | EMP_XB_IX   |      5 |         |       1 (0) |
|  4 |  TABLE ACCESS BY INDEX ROWID  | DEPARTMENT  |      1 |       7 |       1 (0) |
|* 5 |   INDEX UNIQUE SCAN           | DEPT_ID_PK1 |      1 |         |       0 (0) |
-------------------------------------------------------------------------------------

Predicate Information:
----------------------
   1 - filter(>0)
   3 - access("E"."JOB_ID"='IT_PROG')
   5 - access("D"."DEPARTMENT_ID"=:B1)
  • Id 1FILTER 연산이 scalar subquery 가 일반 subquery filter 로 변환되었습니다.
  • Id 4 / Id 5Id 1 의 두 번째 자식이며, outer EMPLOYEE 의 row 마다 DEPARTMENT 를 PK 로 lookup 하는 correlated subquery 형태입니다.
  • Id 5 의 access predicate "D"."DEPARTMENT_ID"=:B1 에서 :B1 이 outer e.department_id 이며, 이것이 FPD 가 적용된 흔적입니다. 만약 변환되지 않았다면 scalar subquery 의 출력 컬럼을 매번 계산한 뒤 outer 에서 >0 비교를 했을 것입니다.

원본 쿼리에 인라인 뷰 a 가 분명히 있는데 plan 에 view operation 이 없는 것도 view merging 이 일어났다는 signature 입니다.


2) 변환 후 형태 (수정 쿼리 = 옵티마이저 변환 결과)

옵티마이저가 자동으로 풀어낸 결과를 사람 손으로 명시적으로 작성하면 다음과 같습니다.

1
2
3
4
5
6
SELECT e.employee_id, e.first_name, e.last_name, e.email
  FROM employee e
 WHERE e.job_id = 'IT_PROG'
   AND (SELECT location_id
          FROM department d
         WHERE d.department_id = e.department_id) > 0;

scalar subquery 였던 (SELECT location_id FROM department ...) 가 그대로 WHERE 절로 이동하여 일반 subquery filter 가 되었습니다.


분석

View Merging

원본 쿼리의 인라인 뷰 a 는 단순한 column projection + filter 만 가지므로 옵티마이저가 view merging 대상으로 판단합니다. View merging 후 쿼리는 다음과 같은 모습이 됩니다.

1
2
3
4
5
6
SELECT e.employee_id, e.first_name, e.last_name, e.email
  FROM employee e
 WHERE e.job_id = 'IT_PROG'
   AND (SELECT location_id
          FROM department d
         WHERE d.department_id = e.department_id) > 0;

이 단계에서 이미 scalar subquery 가 outer WHERE 절로 빠져나옵니다. 이후 옵티마이저가 이 형태를 standard correlated subquery filter 로 처리합니다.

Filter Push Down

엄밀히 말하면 본 케이스의 “FPD” 는 d.department_id = e.department_id 라는 correlation 조건이 inner subquery 안에 이미 들어 있는 형태 라 별도의 push-down 단계가 필요 없습니다. View merging 만으로 scalar subquery 가 outer filter 로 변환되면 plan 의 FILTER 연산이 자연스럽게 형성됩니다.

다만 사용자 원본 자료의 표현을 살리면, “outer > 0 조건이 변환된 subquery 의 outer filter 로 push down 된다” 는 구도로 이해할 수 있습니다. 즉 다음 변환 흐름입니다.

1
2
3
4
5
6
7
[원본]                            [view merging 후]              [FPD 후]

  inline_view a                     scalar subquery 가              correlated subquery
   ├─ select scalar subquery         outer WHERE 로 빠짐              filter 형태 (Id 1)
   └─ filter location_id > 0  →       AND <ss> > 0          →
  outer
                                                            plan: FILTER + EMPLOYEE + DEPARTMENT

Scalar subquery 를 인라인 뷰로 감싸 가독성을 높이는 패턴은 OK 입니다. 옵티마이저가 view merging + FPD 로 풀어내므로 결과 plan 은 똑같이 효율적입니다. 단 scalar subquery 결과를 filter 로 사용하지 않는다면 변환이 일어나지 않고, Plan 내 Inline View 가 그대로 남게 됩니다.


정리

  1. Scalar subquery + 인라인 뷰 + 바깥 filter 패턴은 옵티마이저가 view merging + FPD 로 풀어 일반 subquery filter 로 변환합니다.
  2. 변환 트리거 조건: scalar subquery 결과가 인라인 뷰 바깥에서 filter 로 사용 되어야 합니다. SELECT list 출력만이면 변환되지 않습니다.
  3. 튜닝 의의: 가독성 vs plan 결과의 trade-off 가 없습니다. 인라인 뷰로 감싸도 옵티마이저가 같은 plan 을 만들어주므로 자유롭게 표기를 선택해도 됩니다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.