Oracle/Tuning2019. 9. 2. 14:54

뷰 내부에 ROWNUM 슈도 칼럼을 사용하면 FPD(Filter Push-Down)가 동작하지 않는다. 이로 인해 쿼리 성능이 저하될 수 있다. 이번 글에서는 패키지 변수를 사용하여 FPD가 동작하지 않는 뷰의 성능에 대한 내용을 살펴보자.


테스트를 위해 아래와 같이 테이블을 생성하자.

-- 1
DROP TABLE t1 PURGE;

CREATE TABLE t1 AS SELECT CEIL (ROWNUM / 500) AS c1, ROWNUM AS c2, 'X' AS c3 FROM XMLTABLE ('1 to 1000');

CREATE INDEX t1_x1 ON t1 (c1, c2);
CREATE INDEX t1_x2 ON t1 (c2);

아래 뷰가 성능 개선 대상이다.

-- 2
CREATE OR REPLACE VIEW v1 AS
SELECT *
  FROM (SELECT   *
            FROM t1
        ORDER BY c2 DESC)
 WHERE ROWNUM <= 1;

아래는 뷰를 사용한 쿼리다. 3-2번 쿼리는 결과가 반환되지만, 3-1번 쿼리는 결과가 반환되지 않는다. 뷰가 수행된 후 c1 = 1 조건을 필터링하기 때문이다. 성능 측면에서는 테이블을 전체 스캔한 것을 확인할 수 있다.

-- 3-1
SELECT * FROM v1 WHERE c1 = 1;

선택된 레코드가 없습니다.

---------------------------------------------------------------------
| Id  | Operation                | Name | Starts | A-Rows | Buffers |
---------------------------------------------------------------------
|   0 | SELECT STATEMENT         |      |      1 |      0 |       4 |
|*  1 |  VIEW                    | V1   |      1 |      0 |       4 | -- !
|*  2 |   COUNT STOPKEY          |      |      1 |      1 |       4 |
|   3 |    VIEW                  |      |      1 |      1 |       4 |
|*  4 |     SORT ORDER BY STOPKEY|      |      1 |      1 |       4 |
|   5 |      TABLE ACCESS FULL   | T1   |      1 |   1000 |       4 |
---------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C1"=1)
   2 - filter(ROWNUM<=1)
   4 - filter(ROWNUM<=1)

-- 3-2
SELECT * FROM v1 WHERE c1 = 2;

C1   C2 C3
-- ---- --
 2 1000 X

1개의 행이 선택되었습니다.

---------------------------------------------------------------------
| Id  | Operation                | Name | Starts | A-Rows | Buffers |
---------------------------------------------------------------------
|   0 | SELECT STATEMENT         |      |      1 |      1 |       4 |
|*  1 |  VIEW                    | V1   |      1 |      1 |       4 |
|*  2 |   COUNT STOPKEY          |      |      1 |      1 |       4 |
|   3 |    VIEW                  |      |      1 |      1 |       4 |
|*  4 |     SORT ORDER BY STOPKEY|      |      1 |      1 |       4 |
|   5 |      TABLE ACCESS FULL   | T1   |      1 |   1000 |       4 |
---------------------------------------------------------------------

아래와 같이 패키지를 생성하자. pkg_view_params 패키지에 파라미터를 정의했고, pkg_view_param 패키지로 파라미터를 조회(get), 설정(set)한다.

-- 4
CREATE OR REPLACE PACKAGE pkg_view_params
IS
    c1 VARCHAR2(4000);
    c2 VARCHAR2(4000);
END;
/

CREATE OR REPLACE PACKAGE pkg_view_param
AS
    FUNCTION  fnc_get (i_name VARCHAR2) RETURN VARCHAR2 DETERMINISTIC;
    PROCEDURE prc_set (i_name VARCHAR2, i_value VARCHAR2);
END;
/

CREATE OR REPLACE PACKAGE BODY pkg_view_param
IS
    FUNCTION  fnc_get (i_name VARCHAR2) RETURN VARCHAR2 DETERMINISTIC
    IS
    BEGIN
        RETURN CASE i_name
                   WHEN 'C1' THEN pkg_view_params.c1
                   WHEN 'C2' THEN pkg_view_params.c2
                   ELSE NULL
               END;
    END;

    PROCEDURE prc_set (i_name VARCHAR2, i_value VARCHAR2)
    IS
    BEGIN
        CASE i_name
            WHEN 'C1' THEN pkg_view_params.c1 := i_value;
            WHEN 'C2' THEN pkg_view_params.c2 := i_value;
            ELSE NULL;
        END CASE;
    END;
END;
/

실행 계획을 최적화하기 위해 아래처럼 다소 복잡한 뷰를 작성했다.

-- 5
CREATE OR REPLACE VIEW v2 AS
SELECT *
  FROM (SELECT *
          FROM (SELECT   /*+ INDEX_DESC(T1 (C2)) */
                         *
                    FROM t1
                   WHERE pkg_view_param.fnc_get ('C1') IS NULL     -- !
                     AND c2 IS NOT NULL
                ORDER BY c2 DESC)
         WHERE ROWNUM <= 1)
UNION ALL
SELECT *
  FROM (SELECT *
          FROM (SELECT   /*+ INDEX_DESC(T1 (C1 C2)) */
                         *
                    FROM t1
                   WHERE pkg_view_param.fnc_get ('C1') IS NOT NULL -- !
                     AND c1 = pkg_view_param.fnc_get ('C1')        -- !
                ORDER BY c2 DESC)
         WHERE ROWNUM <= 1);

아래는 pkg_view_param 패키지를 사용한 쿼리다. 6-1번 쿼리도 결과가 반환되었고, 옵션 조건에 따라 최적의 실행계획으로 수행되는 것을 확인할 수 있다.

-- 6-1
EXEC pkg_view_param.prc_set ('C1', '1');

SELECT * FROM v2;

C1  C2 C3
-- --- --
 1 500 X

1개의 행이 선택되었습니다.

--------------------------------------------------------------------------------
| Id  | Operation                          | Name  | Starts | A-Rows | Buffers |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |       |      1 |      1 |       3 |
|   1 |  VIEW                              | V2    |      1 |      1 |       3 |
|   2 |   UNION-ALL                        |       |      1 |      1 |       3 |
|   3 |    VIEW                            |       |      1 |      0 |       0 |
|*  4 |     COUNT STOPKEY                  |       |      1 |      0 |       0 |
|   5 |      VIEW                          |       |      1 |      0 |       0 |
|*  6 |       SORT ORDER BY STOPKEY        |       |      1 |      0 |       0 |
|*  7 |        FILTER                      |       |      1 |      0 |       0 |
|   8 |         TABLE ACCESS FULL          | T1    |      0 |      0 |       0 |
|   9 |    VIEW                            |       |      1 |      1 |       3 |
|* 10 |     COUNT STOPKEY                  |       |      1 |      1 |       3 |
|  11 |      VIEW                          |       |      1 |      1 |       3 |
|* 12 |       FILTER                       |       |      1 |      1 |       3 |
|  13 |        TABLE ACCESS BY INDEX ROWID | T1    |      1 |      1 |       3 |
|* 14 |         INDEX RANGE SCAN DESCENDING| T1_X1 |      1 |      1 |       2 |
--------------------------------------------------------------------------------

-- 6-2
EXEC pkg_view_param.prc_set ('C1', '2');

SELECT * FROM v2;

C1   C2 C3
-- ---- --
 2 1000 X

1개의 행이 선택되었습니다.

--------------------------------------------------------------------------------
| Id  | Operation                          | Name  | Starts | A-Rows | Buffers |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |       |      1 |      1 |       3 |
|   1 |  VIEW                              | V2    |      1 |      1 |       3 |
|   2 |   UNION-ALL                        |       |      1 |      1 |       3 |
|   3 |    VIEW                            |       |      1 |      0 |       0 |
|*  4 |     COUNT STOPKEY                  |       |      1 |      0 |       0 |
|   5 |      VIEW                          |       |      1 |      0 |       0 |
|*  6 |       SORT ORDER BY STOPKEY        |       |      1 |      0 |       0 |
|*  7 |        FILTER                      |       |      1 |      0 |       0 |
|   8 |         TABLE ACCESS FULL          | T1    |      0 |      0 |       0 |
|   9 |    VIEW                            |       |      1 |      1 |       3 |
|* 10 |     COUNT STOPKEY                  |       |      1 |      1 |       3 |
|  11 |      VIEW                          |       |      1 |      1 |       3 |
|* 12 |       FILTER                       |       |      1 |      1 |       3 |
|  13 |        TABLE ACCESS BY INDEX ROWID | T1    |      1 |      1 |       3 |
|* 14 |         INDEX RANGE SCAN DESCENDING| T1_X1 |      1 |      1 |       2 |
--------------------------------------------------------------------------------

-- 6-3
EXEC pkg_view_param.prc_set ('C1', NULL);

SELECT * FROM v2;

C1   C2 C3
-- ---- --
 2 1000 X

1개의 행이 선택되었습니다.

--------------------------------------------------------------------------------
| Id  | Operation                          | Name  | Starts | A-Rows | Buffers |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |       |      1 |      1 |       3 |
|   1 |  VIEW                              | V2    |      1 |      1 |       3 |
|   2 |   UNION-ALL                        |       |      1 |      1 |       3 |
|   3 |    VIEW                            |       |      1 |      1 |       3 |
|*  4 |     COUNT STOPKEY                  |       |      1 |      1 |       3 |
|   5 |      VIEW                          |       |      1 |      1 |       3 |
|*  6 |       FILTER                       |       |      1 |      1 |       3 |
|   7 |        TABLE ACCESS BY INDEX ROWID | T1    |      1 |      1 |       3 |
|*  8 |         INDEX FULL SCAN DESCENDING | T1_X2 |      1 |      1 |       2 |
|   9 |    VIEW                            |       |      1 |      0 |       0 |
|* 10 |     COUNT STOPKEY                  |       |      1 |      0 |       0 |
|  11 |      VIEW                          |       |      1 |      0 |       0 |
|* 12 |       FILTER                       |       |      1 |      0 |       0 |
|  13 |        TABLE ACCESS BY INDEX ROWID | T1    |      0 |      0 |       0 |
|* 14 |         INDEX RANGE SCAN DESCENDING| T1_X1 |      0 |      0 |       0 |
--------------------------------------------------------------------------------


'Oracle > Tuning' 카테고리의 다른 글

DUAL CONNECT BY 쿼리 성능 개선 방안  (0) 2019.09.11
UPDATE 문 성능 개선 사례  (0) 2019.09.03
고유 값에 대한 상관 서브 쿼리 성능 개선 방안  (0) 2019.08.28
Plan Hash Value  (0) 2019.08.26
MERGE JOIN CARTESIAN  (0) 2019.08.23
Posted by 정희락_