{section}
{column:width=50}


C:\>sqlplus scott/tiger

SQL*Plus: Release 10.2.0.1.0 - Production on 화 4월 12 01:17:10 2011

Copyright (c) 1982, 2005, Oracle.  All rights reserved.


다음에 접속됨:
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production

SQL> set linesize 130
SQL> set pagesize 0
SQL> DROP TABLE t1 PURGE;

테이블이 삭제되었습니다.

SQL> DROP TABLE t2 PURGE;

테이블이 삭제되었습니다.

SQL> DROP TABLE t3 PURGE;

테이블이 삭제되었습니다.

SQL> CREATE TABLE t1(c1 INT, c2 CHAR(10), c3 INT);

테이블이 생성되었습니다.

SQL> CREATE TABLE t2(c1 INT, c2 CHAR(10), c3 INT);

테이블이 생성되었습니다.

SQL> CREATE TABLE t3(c1 INT, c2 CHAR(10), c3 INT);

테이블이 생성되었습니다.

SQL> CREATE INDEX t1_n1 ON t1(c1);

인덱스가 생성되었습니다.

SQL> CREATE INDEX t2_n1 ON t2(c1);

인덱스가 생성되었습니다.

SQL> CREATE INDEX t3_n1 ON t3(c1);

인덱스가 생성되었습니다.

SQL> ALTER SESSION SET "_optimizer_cost_based_transformation" = off;

세션이 변경되었습니다.

SQL> ALTER SESSION SET "_optimizer_push_pred_cost_based" = false;

세션이 변경되었습니다.

SQL> INSERT INTO t1
  2  SELECT level              c1    -- 유일값(1~10000)
  3       , 'dummy'            c2    -- 동일값('dummy')
  4       , MOD(level, 10) + 1 c3    -- 10개값(1~10)
  5    FROM dual
  6   CONNECT BY level <= 10000
  7  ;

10000 개의 행이 만들어졌습니다.

SQL> INSERT INTO t2
  2  SELECT level
  3       , 'dummy'
  4       , MOD(level, 10) + 1
  5    FROM dual
  6   CONNECT BY level <= 1000
  7  ;

1000 개의 행이 만들어졌습니다.

SQL> INSERT INTO t3
  2  SELECT level
  3       , 'dummy'
  4       , MOD(level, 10) + 1
  5    FROM dual
  6   CONNECT BY level <= 100
  7  ;

100 개의 행이 만들어졌습니다.

SQL> COMMIT;

커밋이 완료되었습니다.

SQL> EXEC dbms_stats.gather_table_stats(user, 'T1');

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> EXEC dbms_stats.gather_table_stats(user, 'T2');

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> EXEC dbms_stats.gather_table_stats(user, 'T3');

PL/SQL 처리가 정상적으로 완료되었습니다.

SQL> SELECT COUNT(*)
  2    FROM (SELECT /*+ gather_plan_statistics */
  3                 t1.c1, t1.c2
  4            FROM t1
  5           WHERE t1.c1 IN (SELECT /*+ NO_UNNEST */ t2.c1 FROM t2)
  6          )
  7  ;
      1000

SQL> SELECT * FROM TABLE(dbms_xplan.display_cursor(null,null,'allstats cost last'));
SQL_ID  9bm19ht6vrpg5, child number 0
-------------------------------------
SELECT COUNT(*)   FROM (SELECT /*+ gather_plan_statistics */                t1.c1, t1.c2
       FROM t1          WHERE t1.c1 IN (SELECT /*+ NO_UNNEST */ t2.c1 FROM t2)         )

Plan hash value: 645528743

----------------------------------------------------------------------------------------------------
| Id  | Operation           | Name  | Starts | E-Rows | Cost (%CPU)| A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE     |       |      1 |      1 |            |      1 |00:00:00.04 |   20038 |
|*  2 |   FILTER            |       |      1 |        |            |   1000 |00:00:00.01 |   20038 |
|   3 |    TABLE ACCESS FULL| T1    |      1 |  10000 |    11   (0)|  10000 |00:00:00.01 |      38 |
|*  4 |    INDEX RANGE SCAN | T2_N1 |  10000 |      1 |     1   (0)|   1000 |00:00:00.03 |   20000 |
----------------------------------------------------------------------------------------------------

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

   2 - filter( IS NOT NULL)
   4 - access("T2"."C1"=:B1)


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

SQL> SELECT COUNT(*)
  2    FROM (SELECT /*+ gather_plan_statistics */
  3                 t1.c1, t1.c2
  4            FROM t1
  5           WHERE t1.c1 IN (SELECT t2.c1 FROM t2)
  6          )
  7  ;
      1000

SQL> SELECT * FROM TABLE(dbms_xplan.display_cursor(null,null,'allstats cost last'));
SQL_ID  9g821jv5f7kgv, child number 0
-------------------------------------
SELECT COUNT(*)   FROM (SELECT /*+ gather_plan_statistics */                t1.c1, t1.c2           FROM t1          WHERE
t1.c1 IN (SELECT t2.c1 FROM t2)         )

Plan hash value: 911817144

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name  | Starts | E-Rows | Cost (%CPU)| A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE        |       |      1 |      1 |            |      1 |00:00:00.01 |      31 |       |       |          |
|*  2 |   HASH JOIN RIGHT SEMI |       |      1 |    999 |    10  (10)|   1000 |00:00:00.01 |      31 |  1066K|  1066K| 1174K (0)|
|   3 |    INDEX FAST FULL SCAN| T2_N1 |      1 |   1000 |     2   (0)|   1000 |00:00:00.01 |       7 |       |       |          |
|   4 |    INDEX FAST FULL SCAN| T1_N1 |      1 |  10000 |     7   (0)|  10000 |00:00:00.01 |      24 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------

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

   2 - access("T1"."C1"="T2"."C1")


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

SQL>

{column}
{column:width=50}
{column}
{section}

{section}
{column:width=50}

실행계획을 보고 유추한 가상(실제 Semi join 이란 예약어는 존재하지 않음)의 변형된 쿼리


SELECT COUNT(*)
  FROM (SELECT *
          FROM t1 SEMI JOIN t2
         WHERE t1.c1 = t2.c1
        )
;

{column}
{column:width=50}
{column}
{section}

정리

  • Subquery Unnesting 이 일어나지 않을 경우 Filter Operation 이 거의 유일한 방법
  • Subquery Unnest 되어 Join 으로 Transformation 된다면?
    • 다양한 형태의 Operation(조인방식 & 스캔방식) 선택이 가능하다.
    • 위의 예에서는 Index_FFS 에 의한 Hash Join 이 선택되었다.
  • Unnesting 된 Subquery 는
    • Semi Join(In 이나 Exists), Anti Join(Not In 이나 Not Exists), 일반 조인 등으로 변환된다.
    • 최적의 Join 방식 및 Access 방식을 가지도록 실행계획을 수립하도록 한다.

주의

  • Subquery Unnesting 은 무조건 Filter 를 Join으로 바꾼다? (n)(x)
  • Subquery Unnesting 이 실패하면 무조건 Filter 로 풀린다? (n)(x)
  • 모든 것은 실행계획을 통해 실증적으로 검증해야 한다. (y)