Oracle/Tuning2019. 4. 24. 17:36

중복 조회로 랜덤 I/O를 감소시키는 튜닝 기법을 살펴보자.


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

-- 1
DROP TABLE t1 PURGE;
DROP TABLE t2 PURGE;
 
CREATE TABLE t1 AS
SELECT   ROWNUM AS c1, CEIL (ROWNUM / 100) AS c2, LPAD ('X', 100, 'X') AS c3
    FROM XMLTABLE ('1 to 1000000')
ORDER BY ORA_HASH (ROWNUM);
 
 
CREATE TABLE t2 AS
SELECT   ROWNUM AS c1, MOD (ROWNUM, 10000) AS c2, LPAD ('X', 100, 'X') AS c3
    FROM XMLTABLE ('1 to 1000000')
ORDER BY ORA_HASH (ROWNUM);
 
CREATE INDEX t1_x1 ON t1 (c1, c2);
CREATE INDEX t2_x1 ON t2 (c1, c2);

해시 조인시 32,350개의 블록 I/O가 발생한다.

-- 2
SELECT /*+ LEADING(A) USE_HASH(B) FULL(A) FULL(B) */
       *
  FROM t1 a
     , t2 b
 WHERE a.c1 < 10001
   AND b.c1 = a.c2
   AND b.c2 = 1;
 
--------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | A-Rows | Buffers | Used-Mem |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |    100 |   32350 |          |
|*  1 |  HASH JOIN         |      |      1 |    100 |   32350 | 2902K (0)|
|*  2 |   TABLE ACCESS FULL| T1   |      1 |  10000 |   16173 |          |
|*  3 |   TABLE ACCESS FULL| T2   |      1 |    100 |   16174 |          |
--------------------------------------------------------------------------

NL 조인도 10,069개의 블록 I/O가 발생한다.

-- 3
SELECT /*+ LEADING(A) USE_NL(B) INDEX(A) INDEX(B) */
       *
  FROM t1 a
     , t2 b
 WHERE a.c1 < 10001
   AND b.c1 = a.c2
   AND b.c2 = 1;
 
-----------------------------------------------------------------------------------
| Id  | Operation                             | Name  | Starts | A-Rows | Buffers |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |       |      1 |    100 |   10069 |
|   1 |  NESTED LOOPS                         |       |      1 |    100 |   10069 |
|   2 |   NESTED LOOPS                        |       |      1 |    100 |   10061 |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T1    |      1 |  10000 |   10035 |
|*  4 |     INDEX RANGE SCAN                  | T1_X1 |      1 |  10000 |      35 |
|*  5 |    INDEX RANGE SCAN                   | T2_X1 |  10000 |    100 |      26 |
|   6 |   TABLE ACCESS BY INDEX ROWID         | T2    |    100 |    100 |       8 |
-----------------------------------------------------------------------------------

CLUSTER_BY_ROWID 힌트로 CF를 개선시키더라도 7,524개의 블록 I/O가 발생하는 것을 확인할 수 있다.

-- 4
SELECT /*+ LEADING(A) USE_NL(B) INDEX(A) INDEX(B) CLUSTER_BY_ROWID(A) */
       *
  FROM t1 a
     , t2 b
 WHERE a.c1 < 10001
   AND b.c1 = a.c2
   AND b.c2 = 1;
 
----------------------------------------------------------------------------------------------
| Id  | Operation                             | Name  | Starts | A-Rows | Buffers | Used-Mem |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |       |      1 |    100 |    7524 |          |
|   1 |  NESTED LOOPS                         |       |      1 |    100 |    7524 |          |
|   2 |   NESTED LOOPS                        |       |      1 |    100 |    7516 |          |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T1    |      1 |  10000 |    7489 |          |
|   4 |     SORT CLUSTER BY ROWID             |       |      1 |  10000 |      28 |  487K (0)|
|*  5 |      INDEX RANGE SCAN                 | T1_X1 |      1 |  10000 |      28 |          |
|*  6 |    INDEX RANGE SCAN                   | T2_X1 |  10000 |    100 |      27 |          |
|   7 |   TABLE ACCESS BY INDEX ROWID         | T2    |    100 |    100 |       8 |          |
----------------------------------------------------------------------------------------------

아래 쿼리는 t1_x1 인덱스만 읽고 t2 테이블을 조인하여 건수를 줄이고, 그 결과로 t1 테이블을 ROWID로 한번 더 조인함으로써 t1 테이블의 랜덤 액세스를 감소시켰다. 블록 I/O가 169로 감소했다.

-- 5
SELECT /*+ OPT_PARAM('_OPTIMIZER_JOIN_ELIMINATION_ENABLED', 'FALSE') ORDERED USE_NL(B C) */
       c.*, b.*
  FROM t1 a
     , t2 b
     , t1 c
 WHERE a.c1 < 10001
   AND b.c1 = a.c2
   AND b.c2 = 1
   AND c.ROWID = a.ROWID;
 
-----------------------------------------------------------------------------------
| Id  | Operation                             | Name  | Starts | A-Rows | Buffers |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |       |      1 |    100 |     169 |
|   1 |  NESTED LOOPS                         |       |      1 |    100 |     169 |
|   2 |   NESTED LOOPS                        |       |      1 |    100 |      69 |
|*  3 |    INDEX RANGE SCAN                   | T1_X1 |      1 |  10000 |      35 |
|   4 |    TABLE ACCESS BY INDEX ROWID BATCHED| T2    |  10000 |    100 |      34 |
|*  5 |     INDEX RANGE SCAN                  | T2_X1 |  10000 |    100 |      26 |
|   6 |   TABLE ACCESS BY USER ROWID          | T1    |    100 |    100 |     100 | -- !
-----------------------------------------------------------------------------------

아래 쿼리도 비슷한 기법으로 t2 테이블까지 ROWID로 조인하는 방식이다. 블록 I/O는 위 쿼리와 동일하다.

-- 6
SELECT /*+ OPT_PARAM('_OPTIMIZER_JOIN_ELIMINATION_ENABLED', 'FALSE') ORDERED USE_NL(B C D) */
       c.*, d.*
  FROM t1 a
     , t2 b
     , t1 c
     , t2 d
 WHERE a.c1 < 10001
   AND b.c1 = a.c2
   AND b.c2 = 1
   AND c.ROWID = a.ROWID
   AND d.ROWID = b.ROWID;
 
--------------------------------------------------------------------------
| Id  | Operation                    | Name  | Starts | A-Rows | Buffers |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |       |      1 |    100 |     169 |
|   1 |  NESTED LOOPS                |       |      1 |    100 |     169 |
|   2 |   NESTED LOOPS               |       |      1 |    100 |     161 |
|   3 |    NESTED LOOPS              |       |      1 |    100 |      61 |
|*  4 |     INDEX RANGE SCAN         | T1_X1 |      1 |  10000 |      35 |
|*  5 |     INDEX RANGE SCAN         | T2_X1 |  10000 |    100 |      26 |
|   6 |    TABLE ACCESS BY USER ROWID| T1    |    100 |    100 |     100 |
|   7 |   TABLE ACCESS BY USER ROWID | T2    |    100 |    100 |       8 | -- !
--------------------------------------------------------------------------

쿼리가 복잡하고 조인 순서 제어가 어렵다면 Subquery Pushing을 사용할 수도 있다. 서브 쿼리로 입력되는 값이 연속되고 소수인 경우에만 캐싱 효과가 있다. 블록 I/O가 279개로 Buffer Pinning보다는 효과가 크지 않은 것을 확인할 수 있다.

-- 7
SELECT /*+ LEADING(A) USE_NL(B) INDEX(A) INDEX(B) */
       *
  FROM t1 a
     , t2 b
 WHERE a.c1 < 10001
   AND EXISTS (SELECT /*+ NO_UNNEST PUSH_SUBQ */
                          1
                     FROM t2 x
                    WHERE x.c1 = a.c2
                      AND x.c2 = 1)
   AND b.c1 = a.c2
   AND b.c2 = 1;
 
-----------------------------------------------------------------------------------
| Id  | Operation                             | Name  | Starts | A-Rows | Buffers |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |       |      1 |    100 |     279 |
|   1 |  NESTED LOOPS                         |       |      1 |    100 |     279 |
|   2 |   NESTED LOOPS                        |       |      1 |    100 |     271 |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T1    |      1 |    100 |     245 |
|*  4 |     INDEX RANGE SCAN                  | T1_X1 |      1 |    100 |     145 |
|*  5 |      INDEX RANGE SCAN                 | T2_X1 |    100 |      1 |     110 | -- !
|*  6 |    INDEX RANGE SCAN                   | T2_X1 |    100 |    100 |      26 |
|   7 |   TABLE ACCESS BY INDEX ROWID         | T2    |    100 |    100 |       8 |
-----------------------------------------------------------------------------------


Posted by 정희락_