새로쓴 대용량 데이터베이스솔루션 1 (2011년)
Nested Loop형 세미조인 0 0 30,821

by 구루비스터디 세미조인 Semi Join [2023.10.21]


  1. 세미조인의 실행 계획
    1. Nested Loop형 세미조인
    2. 막연하게 연결고리가 많으면 많을수록 유리할거라는 어느 통신사 사례
    3. UNNEST 말고 다른효율적인 방법은 없는것인가??


세미조인의 실행 계획

  • 원리적인 측면에서 볼 때는 결코 일반적인 조인의 범주를 벗어 나지 않는다고 하였다.
  • 그렇다면 결국 가장 보편적인 조인이 Nested Loops 조인이었듯이 세미 조인에서도 대부분의 경우는 Nested Loops조인과 매우 유사하게 처리된다는 것을 뜻한다.


Nested Loop형 세미조인

  • 제공자 : 서브쿼리가 먼저 수행되어 SELECT-List의 견결고리 값을 상수값으로 만들고, 이것을 메인쿼리 연결고리에 대응시키는 방법
  • 확인자 : 메인쿼리가 먼저 수행되어 상수값이 된 연결고리 값을 서브쿼리의 연결고리( 서브쿼리 SELECT-List에 있는 컬럼 )에 제공하는 방법



그림 2-2-24 준비스크립트


11:45:32 SQL> SELECT * FROM V$VERSION WHERE ROWNUM <= 1;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bi

DROP TABLE TAB1 PURGE;
DROP TABLE TAB2 PURGE;

CREATE TABLE TAB1 AS
SELECT LEVEL KEY1
         ,  '상품'||LEVEL COL1
         , TRUNC( dbms_random.value( 1,7 ) ) * 500 + TRUNC( dbms_random.value( 1,3 ) ) * 750 COL2
  FROM DUAL
CONNECT BY LEVEL <= 10000;

CREATE TABLE TAB2 AS
SELECT LEVEL KEY1
         , TRUNC(dbms_random.value( 1, 10000 ) ) KEY2
         , TRUNC( SYSDATE ) - 100 + CEIL( LEVEL/   1000 ) COL1
         , 'A' COL2    
  FROM DUAL
CONNECT BY LEVEL <= 100000;

CREATE INDEX KEY1_IDX ON TAB1( KEY1 );
CREATE INDEX COL1_IDX01 ON TAB2( COL1, COL2 );

EXEC DBMS_STATS.GATHER_TABLE_STATS( USER, 'TAB1' );
EXEC DBMS_STATS.GATHER_TABLE_STATS( USER, 'TAB2' );

SQL> DESC TAB2;
 이름                                                                                                      널?      유형
 ----------------------------------------------------------------------------------------------------------------- -------- -
 KEY1                                                                                                               NUMBER
 KEY2                                                                                                               NUMBER
 COL1                                                                                                               DATE
 COL2                                                                                                               CHAR(1)

SQL> SELECT /*+ gather_plan_statistics */ COUNT( DISTINCT KEY2 )
  2    FROM TAB2 A
  3  WHERE COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  4      AND COL2 = 'A';

COUNT(DISTINCTKEY2)
-------------------
               1810

경   과: 00:00:00.02
SQL> @xplan

---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT GROUP BY                |            |      1 |      1 |      1 |00:00:00.01 |      15 | 83968 | 83968 |73728  (0)|
|*  2 |   FILTER                      |            |      1 |        |   2000 |00:00:00.01 |      15 |       |       |          |
|   3 |    TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |   2000 |   2000 |00:00:00.01 |      15 |       |       |          |
|*  4 |     INDEX SKIP SCAN           | COL1_IDX01 |      1 |     40 |   2000 |00:00:00.01 |       8 |       |       |          |
---------------------------------------------------------------------------------------------------------------------------------

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

   2 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))
   4 - access("COL1">=TRUNC(SYSDATE@!-1) AND "COL2"='A' AND "COL1"<=TRUNC(SYSDATE@!))
       filter("COL2"='A')


NO_HINT 옵티마이져가 서브쿼리를 조인으로 변형시킴 ( 그림 2-2-24 )


SQL> SELECT /*+ gather_plan_statistics */  COUNT( * )
  2    FROM TAB1 A
  3  WHERE KEY1 IN ( SELECT KEY2
  4                             FROM TAB2 B
  5                           WHERE COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  6                               AND COL2 = 'A' ) ;

  COUNT(*)
----------
      1810

경   과: 00:00:00.02
SQL> @xplan

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE                |            |      1 |      1 |      1 |00:00:00.01 |      41 |       |       |          |
|*  2 |   FILTER                       |            |      1 |        |   1810 |00:00:00.01 |      41 |       |       |          |
|*  3 |    HASH JOIN RIGHT SEMI        |            |      1 |   1830 |   1810 |00:00:00.01 |      41 |  1517K|  1517K| 1277K (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |   2000 |   2000 |00:00:00.01 |      15 |       |       |          |
|*  5 |      INDEX SKIP SCAN           | COL1_IDX01 |      1 |     40 |   2000 |00:00:00.01 |       8 |       |       |          |
|   6 |     INDEX FAST FULL SCAN       | KEY1_IDX   |      1 |  10000 |  10000 |00:00:00.01 |      26 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------

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

   2 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))
   3 - access("KEY1"="KEY2")
   5 - access("COL1">=TRUNC(SYSDATE@!-1) AND "COL2"='A' AND "COL1"<=TRUNC(SYSDATE@!))
       filter("COL2"='A')


  • HASH JOIN SEMI ( Semi/Anti/Outer ) : Oracle 10g
  • Semi/Anti Join의 단점을 보안
  • Semi/Anti Join의 단점.? Semi/Anti Join의 경우 서브쿼리는 항상 후행집합이 될수 밖에 없다.
    Hash Outer Join의 경우도 마찬가지로(+) 표시가 붙는 쪽의 집합은 항상 후행집합이 될 수 밖에 없다.
    하지만 10g 부터 Hash join Right( Semi/Anti/Outer ) 기능이 나오게 되면서 서브쿼리 혹은 아우터 Join 되는쪽의 집합이 선행집합이 될 수 있다.
  • Right.? left 집합 대신에 right( 후행집합 )을 선행집합으로 하겠다는 뜻이다.


HASH JOIN RIGHT SEMI을 힌트로 유도하는 법( 그림 2-2-24 )


SQL> SELECT /*+ gather_plan_statistics  */  COUNT( * )
  2    FROM TAB1 A
  3  WHERE KEY1 IN ( SELECT /*+  HASH_SJ  */  KEY2
  4                             FROM TAB2 B
  5                           WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  6                               AND B.COL2 = 'A'
  7                                ) ;

  COUNT(*)
----------
      1810

경   과: 00:00:00.01
SQL> @XPLAN

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE                |            |      1 |      1 |      1 |00:00:00.01 |      40 |       |       |          |
|*  2 |   FILTER                       |            |      1 |        |   1810 |00:00:00.01 |      40 |       |       |          |
|*  3 |    HASH JOIN RIGHT SEMI        |            |      1 |   1830 |   1810 |00:00:00.01 |      40 |  1517K|  1517K| 1274K (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |   2000 |   2000 |00:00:00.01 |      15 |       |       |          |
|*  5 |      INDEX SKIP SCAN           | COL1_IDX01 |      1 |     40 |   2000 |00:00:00.01 |       8 |       |       |          |
|   6 |     INDEX FAST FULL SCAN       | KEY1_IDX   |      1 |  10000 |  10000 |00:00:00.01 |      25 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------

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

   2 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))
   3 - access("KEY1"="KEY2")
   5 - access("B"."COL1">=TRUNC(SYSDATE@!-1) AND "B"."COL2"='A' AND "B"."COL1"<=TRUNC(SYSDATE@!))
       filter("B"."COL2"='A')



UNNEST 유도( 그림 2-2-24 ) : ㅠㅠ 쿼리 블럭명을 지정해야것군. ( 제공자 )


SQL> SELECT /*+ gather_plan_statistics  */  COUNT( * )
  2    FROM TAB1 A
  3  WHERE KEY1 IN ( SELECT /*+  UNNEST */  KEY2
  4                             FROM TAB2 B
  5                           WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  6                               AND B.COL2 = 'A'
  7                                ) ;

  COUNT(*)
----------
      1810

경   과: 00:00:00.01
SQL> @XPLAN

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE                |            |      1 |      1 |      1 |00:00:00.01 |      41 |       |       |          |
|*  2 |   FILTER                       |            |      1 |        |   1810 |00:00:00.01 |      41 |       |       |          |
|*  3 |    HASH JOIN RIGHT SEMI        |            |      1 |   1830 |   1810 |00:00:00.01 |      41 |  1517K|  1517K| 1552K (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |   2000 |   2000 |00:00:00.01 |      15 |       |       |          |
|*  5 |      INDEX SKIP SCAN           | COL1_IDX01 |      1 |     40 |   2000 |00:00:00.01 |       8 |       |       |          |
|   6 |     INDEX FAST FULL SCAN       | KEY1_IDX   |      1 |  10000 |  10000 |00:00:00.01 |      26 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------

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

   2 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))
   3 - access("KEY1"="KEY2")
   5 - access("B"."COL1">=TRUNC(SYSDATE@!-1) AND "B"."COL2"='A' AND "B"."COL1"<=TRUNC(SYSDATE@!))
       filter("B"."COL2"='A')



UNNEST 유도( 그림 2-2-24 ) : 옵티마이져가 서브쿼리를 조인으로 변형시킴 )


SQL> SELECT /*+ gather_plan_statistics  UNNEST( @SUB ) */  COUNT( * )
  2    FROM TAB1 A
  3  WHERE KEY1 IN ( SELECT /*+  QB_NAME( SUB )*/  KEY2
  4                             FROM TAB2 B
  5                           WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  6                               AND B.COL2 = 'A'
  7                                ) ;

  COUNT(*)
----------
      1810

경   과: 00:00:00.01
SQL> @XPLAN
-- KEY1_IDX 인덱스가 유니크인덱스가 아니라서 패스트 풀스켄으로 옵티마이져 선택해서 해쉬로 풀리는 듯.
-----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE                 |            |      1 |      1 |      1 |00:00:00.01 |      41 |       |       |          |
|*  2 |   FILTER                        |            |      1 |        |   1810 |00:00:00.01 |      41 |       |       |          |
|*  3 |    HASH JOIN                    |            |      1 |   2000 |   1810 |00:00:00.01 |      41 |  1517K|  1517K| 1292K (0)|
|   4 |     SORT UNIQUE                 |            |      1 |   2000 |   1810 |00:00:00.01 |      15 | 83968 | 83968 |73728  (0)|
|   5 |      TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |   2000 |   2000 |00:00:00.01 |      15 |       |       |          |
|*  6 |       INDEX SKIP SCAN           | COL1_IDX01 |      1 |     40 |   2000 |00:00:00.01 |       8 |       |       |          |
|   7 |     INDEX FAST FULL SCAN        | KEY1_IDX   |      1 |  10000 |  10000 |00:00:00.01 |      26 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------

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

   2 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))
   3 - access("KEY1"="KEY2")
   6 - access("B"."COL1">=TRUNC(SYSDATE@!-1) AND "B"."COL2"='A' AND "B"."COL1"<=TRUNC(SYSDATE@!))
       filter("B"."COL2"='A')



  • Unnesting 서브쿼리 : 옵티마이져가 서브 쿼리를 조인으로 변형시킴
  • 서브쿼리를 조인으로 바꾸는 방식 ( 일반적으로 서브쿼리의 테이블이 Driving이된다. )
  • 오라클은 서브쿼리를 인라뷰로 바꾸고 서브쿼리 집합이 DISTINCT하지 않을 경우 Sort Unique나 Hash Unique작업을 추가로 진행한다.
  • 힌트
    • 유도 힌트 : /*+ unnest */ ( 서브쿼리에 사용하거나 메인 쿼리에서 쿼리 블럭 힌트( qb_name )를 사용하여야 한다. )
    • 방지 힌트 : /*+ no_unnest */ ( 서브쿼리에 사용 )


DROP INDEX KEY1_IDX ( KEY1_IDX 인덱스가 유니크인덱스가 아니라서 패스트 풀스켄으로 옵티마이져 선택해서 해쉬로 풀리는 듯.)


DROP INDEX KEY1_IDX

CREATE UNIQUE INDEX KEY1_IDX ON TAB1( KEY1 )



UNNEST 유도( 그림 2-2-24 ) ^^


SQL> SELECT /*+ gather_plan_statistics  UNNEST( @SUB )*/  COUNT( * )
  2    FROM TAB1 A
  3  WHERE KEY1 IN ( SELECT /*+  QB_NAME( SUB ) */ KEY2
  4                             FROM TAB2 B
  5                           WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  6                               AND B.COL2 = 'A'
  7                                ) ;

  COUNT(*)
----------
      1810

경   과: 00:00:00.01
SQL> @XPLAN

-----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE                 |            |      1 |      1 |      1 |00:00:00.01 |    1827 |       |       |          |
|*  2 |   FILTER                        |            |      1 |        |   1810 |00:00:00.01 |    1827 |       |       |          |
|   3 |    NESTED LOOPS                 |            |      1 |   2000 |   1810 |00:00:00.01 |    1827 |       |       |          |
|   4 |     SORT UNIQUE                 |            |      1 |   2000 |   1810 |00:00:00.01 |      15 | 83968 | 83968 |73728  (0)|
|   5 |      TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |   2000 |   2000 |00:00:00.01 |      15 |       |       |          |
|*  6 |       INDEX SKIP SCAN           | COL1_IDX01 |      1 |     40 |   2000 |00:00:00.01 |       8 |       |       |          |
|*  7 |     INDEX UNIQUE SCAN           | KEY1_IDX   |   1810 |      1 |   1810 |00:00:00.01 |    1812 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------

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

   2 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))

   6 - access("B"."COL1">=TRUNC(SYSDATE@!-1) AND "B"."COL2"='A' AND "B"."COL1"<=TRUNC(SYSDATE@!))
       filter("B"."COL2"='A')
   7 - access("KEY1"="KEY2")
  


옵티마이져가 변형한쿼리

SQL> SELECT /*+ gather_plan_statistics LEADING( B A )  USE_NL( A B ) */ COUNT(*)
  2    FROM TAB1 A
  3           , (SELECT DISTINCT KEY2
  4                FROM TAB2 B
  5              WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  6                  AND B.COL2 = 'A' ) B
  7  WHERE A.KEY1 = B.KEY2;

  COUNT(*)
----------
      1810

경   과: 00:00:00.01
SQL> @XPLAN

---------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE                  |            |      1 |      1 |      1 |00:00:00.01 |    1827 |
|   2 |   NESTED LOOPS                   |            |      1 |   1829 |   1810 |00:00:00.01 |    1827 |
|   3 |    VIEW                          |            |      1 |   1829 |   1810 |00:00:00.01 |      15 |
|   4 |     HASH UNIQUE                  |            |      1 |   1829 |   1810 |00:00:00.01 |      15 |
|*  5 |      FILTER                      |            |      1 |        |   2000 |00:00:00.01 |      15 |
|   6 |       TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |   2000 |   2000 |00:00:00.01 |      15 |
|*  7 |        INDEX SKIP SCAN           | COL1_IDX01 |      1 |     40 |   2000 |00:00:00.01 |       8 |
|*  8 |    INDEX UNIQUE SCAN             | KEY1_IDX   |   1810 |      1 |   1810 |00:00:00.01 |    1812 |
---------------------------------------------------------------------------------------------------------

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

   5 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))
   7 - access("B"."COL1">=TRUNC(SYSDATE@!-1) AND "B"."COL2"='A' AND "B"."COL1"<=TRUNC(SYSDATE@!))
       filter("B"."COL2"='A')
   8 - access("A"."KEY1"="B"."KEY2")



NO_UNNEST ( Filter 서브쿼리 : 쿼리 변형없음 )


SQL> SELECT /*+ gather_plan_statistics  */  COUNT( * )
  2    FROM TAB1 A
  3  WHERE KEY1 IN ( SELECT /*+ NO_UNNEST */ KEY2
  4                             FROM TAB2 B
  5                           WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  6                               AND B.COL2 = 'A'
  7                                ) ;

  COUNT(*)
----------
      1801

SQL> @XPLAN

-------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE                |            |      1 |      1 |      1 |00:00:12.22 |     138K|
|*  2 |   FILTER                       |            |      1 |        |   1801 |00:00:13.86 |     138K|
|   3 |    TABLE ACCESS FULL           | TAB1       |      1 |  10000 |  10000 |00:00:00.01 |      33 |
|*  4 |    FILTER                      |            |  10000 |        |   1801 |00:00:12.21 |     138K|
|*  5 |     TABLE ACCESS BY INDEX ROWID| TAB2       |  10000 |      1 |   1801 |00:00:12.18 |     138K|
|*  6 |      INDEX SKIP SCAN           | COL1_IDX01 |  10000 |     46 |     18M|00:00:00.09 |   74061 |
-------------------------------------------------------------------------------------------------------

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

   2 - filter( IS NOT NULL)
   4 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))
   5 - filter("KEY2"=:B1)
   6 - access("B"."COL1">=TRUNC(SYSDATE@!-1) AND "B"."COL2"='A' AND
              "B"."COL1"<=TRUNC(SYSDATE@!))
       filter("B"."COL2"='A')
 


  • Filter 서브쿼리 : 쿼리변형이 없음
  • 흔히 말하는 확인자 서브쿼리임.( 메인쿼리의 값을 제공받아 서브쿼리에서 체크하는 방식임
  • 위의 1번 2번과 다르게 Plan에 메인 쿼리와 서브쿼리의 Join이 없고 Filter 로 나온다.
  • Filter SubQuery의 특징은 메인쿼리의 From 절에 있는 모든 테이블을 엑세스후에 가장 마지막에 서브쿼리가 실행된다.
  • 힌트: 특별한 힌트없음
  • 다만 /*+ no_unnest */ 를 사용하여 SubQuery Flattening 을 방지하고 메인쿼리로부터 제공되는 서브쿼리의 조인컬럼에 인덱스가 생성되어 있으면됨.


바로 위 쿼리 플렌 보충 쿼리들..

바로 위 쿼리 플렌 보충 쿼리 1
SQL>  SELECT /*+ gather_plan_statistics  */  KEY1
  2     FROM TAB1 A
  3   WHERE KEY1 = 1
  4      AND KEY1 IN ( SELECT /*+ NO_UNNEST */ KEY2
  5                              FROM TAB2 B
  6                            WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  7                                AND B.COL2 = 'A'
  8                                 ) ;

      KEY1
----------
         1

SQL> @XPLAN

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------
|*  1 |  INDEX UNIQUE SCAN            | KEY1_IDX   |      1 |      1 |      1 |00:00:00.01 |      15 |
|*  2 |   FILTER                      |            |      1 |        |      1 |00:00:00.01 |      13 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |      1 |      1 |00:00:00.01 |      13 |
|*  4 |     INDEX SKIP SCAN           | COL1_IDX01 |      1 |     46 |   1685 |00:00:00.01 |       7 |
------------------------------------------------------------------------------------------------------

--바로 위 쿼리 플렌 보충 쿼리 2
SQL>  SELECT /*+ gather_plan_statistics  */  KEY1
  2     FROM TAB1 A
  3   WHERE KEY1 = 2
  4      AND KEY1 IN ( SELECT /*+ NO_UNNEST */ KEY2
  5                              FROM TAB2 B
  6                            WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  7                                AND B.COL2 = 'A'
  8                                 ) ;

      KEY1
----------
         2

SQL> @XPLAN

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------
|*  1 |  INDEX UNIQUE SCAN            | KEY1_IDX   |      1 |      1 |      1 |00:00:00.01 |      15 |
|*  2 |   FILTER                      |            |      1 |        |      1 |00:00:00.01 |      13 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| TAB2       |      1 |      1 |      1 |00:00:00.01 |      13 |
|*  4 |     INDEX SKIP SCAN           | COL1_IDX01 |      1 |     46 |   1745 |00:00:00.01 |       7 |
------------------------------------------------------------------------------------------------------

--바로 위 쿼리 플렌 보충 쿼리 3
SQL>  SELECT /*+ gather_plan_statistics  */  KEY1
  2     FROM TAB1 A
  3   WHERE KEY1 IN ( 1, 2 )
  4      AND KEY1 IN ( SELECT /*+ NO_UNNEST */ KEY2
  5                              FROM TAB2 B
  6                            WHERE B.COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  7                                AND B.COL2 = 'A'
  8                                 ) ;

      KEY1
----------
         1
         2

SQL> @XPLAN

-------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-------------------------------------------------------------------------------------------------------
|   1 |  INLIST ITERATOR               |            |      1 |        |      2 |00:00:00.01 |      31 |
|*  2 |   INDEX RANGE SCAN             | KEY1_IDX   |      2 |      1 |      2 |00:00:00.01 |      31 |
|*  3 |    FILTER                      |            |      2 |        |      2 |00:00:00.01 |      26 |
|*  4 |     TABLE ACCESS BY INDEX ROWID| TAB2       |      2 |      1 |      2 |00:00:00.01 |      26 |
|*  5 |      INDEX SKIP SCAN           | COL1_IDX01 |      2 |     46 |   3430 |00:00:00.01 |      14 |
-------------------------------------------------------------------------------------------------------



다른 방법은 없는가.? Driving Semi Join


SQL> SELECT /*+ gather_plan_statistics  SEMIJOIN_DRIVER(@SUB)   */  COUNT( * )
  2    FROM TAB1 A
  3  WHERE KEY1 IN ( SELECT /*+ QB_NAME( SUB )* */ KEY2
  4                             FROM TAB2 B
  5                           WHERE COL1 BETWEEN TRUNC( SYSDATE - 1 ) AND TRUNC( SYSDATE )
  6                               AND COL2 = 'A' ) ;

  COUNT(*)
----------
      1810

경   과: 00:00:00.02
SQL> @xplan

-------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE                   |            |      1 |      1 |      1 |00:00:00.02 |    2020 |       |       |          |
|   2 |   BITMAP CONVERSION COUNT         |            |      1 |   2000 |      1 |00:00:00.02 |    2020 |       |       |          |
|   3 |    BITMAP MERGE                   |            |      1 |        |      1 |00:00:00.02 |    2020 |  1024K|   512K|  416K (0)|
|   4 |     BITMAP KEY ITERATION          |            |      1 |        |   2000 |00:00:00.02 |    2020 |       |       |          |
|*  5 |      FILTER                       |            |      1 |        |   2000 |00:00:00.01 |      15 |       |       |          |
|   6 |       TABLE ACCESS BY INDEX ROWID | TAB2       |      1 |   2000 |   2000 |00:00:00.01 |      15 |       |       |          |
|*  7 |        INDEX SKIP SCAN            | COL1_IDX01 |      1 |     40 |   2000 |00:00:00.01 |       8 |       |       |          |
|   8 |      BITMAP CONVERSION FROM ROWIDS|            |   2000 |        |   2000 |00:00:00.01 |    2005 |       |       |          |
|*  9 |       INDEX RANGE SCAN            | KEY1_IDX   |   2000 |        |   2000 |00:00:00.01 |    2005 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------------------

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

   5 - filter(TRUNC(SYSDATE@!-1)<=TRUNC(SYSDATE@!))
   7 - access("COL1">=TRUNC(SYSDATE@!-1) AND "COL2"='A' AND "COL1"<=TRUNC(SYSDATE@!))
       filter("COL2"='A')
   9 - access("KEY1"="KEY2")



  • Semi Join의 변형된 형태로서 Hash Join의 장점( 조인건수의 최소화 )과 Nested Loop Join의 장점( 성행테이블의 상수화 되어 후행 테이블에서 인덱스를 효율적으로 사용)을 합친 개념이다.
  • Driving Semi Join이라는 Title 자체의 의미대로 Semi Join이 Driving집합이 되는것은 당연하다.
  • BITMAP KEY ITERATION : INLIST ITERATOR Operation 같다고 볼수있다.
  • _b_tree_bitmap_plans = true
  • Oracle 9i이상
  • 오라클 정식 힌트가 아님( ㄷㄷ )



그림 2-2-25 준비스크립트


DROP TABLE 사원 PURGE;

DROP TABLE 가족 PURGE;

CREATE TABLE 사원 AS 
SELECT LEVEL   AS 사번
         , '아무개'||LEVEL AS 성명
         , TRUNC( dbms_random.value( 1,7 ) ) 직급
         , TRUNC( SYSDATE ) - 100 + CEIL( LEVEL/   10000 ) 입사일
         , DECODE( TRUNC( dbms_random.value( 1,32 ) ), 1, 'DB1팀', 2, 'DB2팀', 3, 'DB3팀'
					             , 4, '시스템1팀', 5, '시스템2팀', 6, '시스템3팀'                                                                                   
						     , 7, '경리1과', 8, '경리2과', 9, '경리3과', 10, '경리과', 11, '경리4과'
						     , 12, '개발1팀', 13, '개발2팀', 14, '개발3팀', 15, '개발4팀', 16, '개발5팀', 17, '개발6팀'
						     , 18, 'MD1팀', 19, 'MD2팀', 20, 'MD3팀', 21, 'MD4팀'
						     , 22, '디자인1팀', 23, '디자인2팀', 24, '디자인3팀', 25, '디자인4팀'
						     , 26, '멀티미디어1팀', 27, '멀티미디어2팀', 28, '멀티미디어3팀'
						     , 29, '모바일1팀'
						     , 30, '웹모바일팀'
						     , 31, '마케팅팀' 
						     , 32, '기획팀' ) 부서 
  FROM DUAL
CONNECT BY LEVEL <= 1000000

CREATE TABLE 가족 AS
SELECT 사번, LV 가족번호, '아무개'||LV 성명
         , '19'|| TRUNC( dbms_random.value( 50,110 ) )|| TO_CHAR( TRUNC( dbms_random.value( 1,12 ) ), 'FM09' )|| TO_CHAR( TRUNC( dbms_random.value( 1,28 ) ), 'FM09' ) 생년월일
  FROM 사원 A
         , (SELECT LEVEL LV FROM DUAL CONNECT BY LEVEL <= 10 ) B
WHERE A.직급 >= B.LV


CREATE INDEX 부서_INDEX ON 사원 ( 부서, 사번 )

CREATE UNIQUE INDEX PK_INDEX ON 가족 ( 사번, 가족번호 )

SQL> SELECT 부서, COUNT(*)
  2    FROM 사원
  3   GROUP BY 부서
  4  ;

부서            COUNT(*)
------------- ----------
멀티미디어1팀      31437
시스템2팀          31200
개발4팀            31232
시스템3팀          31172
DB3팀              30931
MD3팀              30972
경리1과            31134
개발1팀            31243
웹모바일팀         31341
개발2팀            31302
모바일1팀          31425

부서            COUNT(*)
------------- ----------
경리3과            31263
개발3팀            31554
기획팀             31291
경리2과            31012
MD1팀              31160
DB2팀              31518
디자인2팀          31272
멀티미디어2팀      31276
경리4과            31075
개발5팀            31409
개발6팀            31425

부서            COUNT(*)
------------- ----------
DB1팀              31310
디자인1팀          31094
멀티미디어3팀      31227
시스템1팀          31249
디자인3팀          31237
마케팅팀           31364
경리과             31346
MD2팀              31126
MD4팀              31113
디자인4팀          31290

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

경   과: 00:00:00.38


SQL>



그림 2-2-25 실행


SQL> SELECT /*+ gather_plan_statistics */ COUNT(*)
  2    FROM 사원 X
  3   WHERE 부서 = '경리과'
  4     AND 사번 IN (SELECT 사번
  5                    FROM 가족 Y
  6                   WHERE Y.사번 = X.사번 --(a)
  7                     AND Y.생년월일 < '20051001' );

  COUNT(*)
----------
     30828

SQL> @xplan

------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation           | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE     |          |      1 |      1 |      1 |00:00:00.69 |   14633 |   9632 |    |          |          |
|*  2 |   HASH JOIN SEMI    |          |      1 |  26199 |  30828 |00:00:00.40 |   14633 |   9632 |  1935K|  1935K| 2195K (0)|
|*  3 |    INDEX RANGE SCAN | 부서_INDE|      1 |  26199 |  31346 |00:00:00.01 |     103 |      0 |    |          |          |
|*  4 |    TABLE ACCESS FULL| 가족     |      1 |   2901K|   3175K|00:00:00.02 |   14530 |   9632 |    |          |          |
------------------------------------------------------------------------------------------------------------------------------

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

   2 - access("사번"="사번")
   3 - access("부서"='경리과')
   4 - filter("Y"."생년월일"<'20051001')



너 왜 인덱스 안타냐?? 더 고비용이냐?? ㅠ( 음.. NL 더 고비용이군 )


SQL> SELECT /*+ gather_plan_statistics */ COUNT(*)
  2    FROM 사원 X
  3   WHERE 부서 = '경리과'
  4     AND 사번 IN (SELECT /*+ INDEX( Y  PK_INDEX ) */ 사번
  5                    FROM 가족 Y
  6                   WHERE Y.사번 = X.사번 --(a)
  7                     AND Y.생년월일 < '20051001' );

  COUNT(*)
----------
     30828

SQL> @XPLAN

-------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
-------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE               |          |      1 |      1 |      1 |00:00:00.35 |   96727 |  17 |
|   2 |   NESTED LOOPS SEMI           |          |      1 |  26199 |  30828 |00:00:00.34 |   96727 |  17 |
|*  3 |    INDEX RANGE SCAN           | 부서_INDE|      1 |  26199 |  31346 |00:00:00.01 |     103 |   0 |
|*  4 |    TABLE ACCESS BY INDEX ROWID| 가족     |  31346 |   2901K|  30828 |00:00:00.31 |   96624 |  17 |
|*  5 |     INDEX RANGE SCAN          | PK_INDEX |  31346 |      1 |  33915 |00:00:00.15 |   62709 |  15 |
-------------------------------------------------------------------------------------------------------------

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

   3 - access("부서"='경리과')
   4 - filter("Y"."생년월일"<'20051001')
   5 - access("사번"="사번")




  • 서브쿼리 내에 메인 쿼리의 컬럼이 존재 한는것은 곧 종속성을 의미한다. ( 이 서브쿼리는 논리적으로 절대 먼저 수행 될 수 없도록 종속되었다는 것이다.
  • 위 쿼리는 잘못된 SQL이다. 왜? (a)라인을 삭제하면 연결이 안 될 것이라고 생각


음.. (a)을 배재함.. ( 으흠.. )

SQL> SELECT /*+ gather_plan_statistics LEADING( @SUB ) */ COUNT(*)
  2    FROM 사원 X
  3   WHERE 부서 = '경리과'
  4     AND 사번 IN (SELECT /*+  QB_NAME( SUB ) */ 사번
  5                            FROM 가족 Y
  6                          WHERE 1 = 1 --Y.사번 = X.사번 --(a)
  7                               AND Y.생년월일 < '20051001' );

  COUNT(*)
----------
     30828

SQL> @XPLAN

---------------------------------------------------------------------------------------------------------------------
| Id  | Operation           | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE     |          |      1 |      1 |      1 |00:00:00.61 |   14633 |       |    |     |
|*  2 |   HASH JOIN SEMI    |          |      1 |  26199 |  30828 |00:00:00.35 |   14633 |  1935K|  1935K| 2187K (0)|
|*  3 |    INDEX RANGE SCAN | 부서_INDE|      1 |  26199 |  31346 |00:00:00.01 |     103 |       |    |     |
|*  4 |    TABLE ACCESS FULL| 가족     |      1 |   2901K|   3175K|00:00:00.01 |   14530 |       |    |     |
---------------------------------------------------------------------------------------------------------------------

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

   2 - access("사번"="사번")
   3 - access("부서"='경리과')
   4 - filter("Y"."생년월일"<'20051001')



강제로 해쉬 조인 라이트 세미는 못푸는 것인가.??


SQL> SELECT /*+ gather_plan_statistics  SWAP_JOIN_INPUTS(@SUB Y) */ COUNT(*)
  2    FROM 사원 X
  3   WHERE 부서 = '경리과'
  4     AND 사번 IN (SELECT /*+  QB_NAME( SUB ) */ 사번
  5                            FROM 가족 Y
  6                          WHERE 1 = 1 --Y.사번 = X.사번 --(a)
  7                               AND Y.생년월일 < '20051001' );

  COUNT(*)
----------
     30828

SQL> @xplan

---------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|
---------------------------------------------------------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE       |          |      1 |      1 |      1 |00:01:38.58 |   14633 |  12586 |   4371 |       |       |          |         |
|*  2 |   HASH JOIN RIGHT SEMI|          |      1 |  26199 |  30828 |00:01:38.20 |   14633 |  12586 |   4371 |    84M|  7739K|  111M (1)|   39936 |
|*  3 |    TABLE ACCESS FULL  | 가족     |      1 |   2901K|   3175K|00:01:22.56 |   14530 |   8215 |      0 |       |       |          |         |
|*  4 |    INDEX RANGE SCAN   | 부서_INDE|      1 |  26199 |  31346 |00:00:00.01 |     103 |      0 |      0 |       |       |          |         |
---------------------------------------------------------------------------------------------------------------------------------------------------

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

   2 - access("사번"="사번")
   3 - filter("Y"."생년월일"<'20051001')
   4 - access("부서"='경리과')



( 참고 ) Early Filter 서브쿼리 제공자 쿼리로 보이지만 확인자 쿼리라고함.


SQL> SELECT /*+ gather_plan_statistics  */ COUNT(*)
  2    FROM 사원 X
  3   WHERE 부서 = '경리과'
  4     AND 사번 IN (SELECT /*+  NO_UNNEST PUSH_SUBQ */ 사번
  5                            FROM 가족 Y
  6                          WHERE Y.사번 = X.사번 --(a)
  7                               AND Y.생년월일 < '20051001' );

  COUNT(*)
----------
     30828

경   과: 00:00:00.37
SQL> @XPLAN

----------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE               |          |      1 |      1 |      1 |00:00:00.35 |     128K|
|*  2 |   INDEX RANGE SCAN            | 부서_INDE|      1 |   1310 |  30828 |00:00:00.34 |     128K|
|*  3 |    TABLE ACCESS BY INDEX ROWID| 가족     |  31346 |  29019 |  30828 |00:00:00.30 |     127K|
|*  4 |     INDEX RANGE SCAN          | PK_INDEX |  31346 |  12806 |  33915 |00:00:00.14 |   94053 |
----------------------------------------------------------------------------------------------------

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

   2 - access("부서"='경리과')
       filter( IS NOT NULL)
   3 - filter("Y"."생년월일"<'20051001')
   4 - access("사번"=:B1)


  • Early Filter 서브쿼리 : 쿼리변형 없음
  • Filter SubQuery와 같은 방식이지만 서브 쿼리를 최대한먼저 실행하여 데이터를 걸러낸다.
  • 힌트 : 메인 쿼리에 push_subq 힌트사용 ( 10g 이후부터는 서브쿼리에 힌트사용해야함 )
  • 주의사항 : 많은 튜닝책에서 "Push_subq 힌트를 사용하면 제공자 서브쿼리를 유도한다" 라고 되어 있으나 이는 잘못된 것이다.
  • push_subq힌트를 사용하면 확인자 서브쿼리( Filter 서브쿼리)를 유도하지만 최대한 먼저 수행한다.
  • Early Filter 서브쿼리 효과는 최소 메인 절에 두가지 테이블이 존재 해야만 눈으로 확인 할수있다.
  • http://scidb.tistory.com/entry/SubQuery-Using-Method-1
  • http://scidb.tistory.com/entry/Using-sub-query-method-Filter-Access-sub-Query


그림 2-2-25 ( 위 쿼리가 정말 Early Filter 서브쿼리인가? ( 솔까 잘모르지만.. 테스트 결과 맞는거 같다. )



SQL> SELECT /*+ gather_plan_statistics  */ COUNT(*)
  2    FROM 사원 X
  3   WHERE 부서 = '경리과'
  4     AND 사번 IN (SELECT /*+  NO_UNNEST NO_PUSH_SUBQ */ 사번
  5                            FROM 가족 Y
  6                          WHERE Y.사번 = X.사번 --(a)
  7                               AND Y.생년월일 < '20051001' );

  COUNT(*)
----------
     30828

경   과: 00:00:00.37
SQL> @XPLAN

Plan hash value: 2622244588

----------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------
|   1 |  SORT AGGREGATE               |          |      1 |      1 |      1 |00:00:00.36 |     128K|
|*  2 |   FILTER                      |          |      1 |        |  30828 |00:00:00.37 |     128K|
|*  3 |    INDEX RANGE SCAN           | 부서_INDE|      1 |  26199 |  31346 |00:00:00.01 |     103 |
|*  4 |    TABLE ACCESS BY INDEX ROWID| 가족     |  31346 |  29019 |  30828 |00:00:00.30 |     127K|
|*  5 |     INDEX RANGE SCAN          | PK_INDEX |  31346 |  12806 |  33915 |00:00:00.14 |   94053 |
----------------------------------------------------------------------------------------------------

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

   2 - filter( IS NOT NULL)
   3 - access("부서"='경리과')
   4 - filter("Y"."생년월일"<'20051001')
   5 - access("사번"=:B1)



막연하게 연결고리가 많으면 많을수록 유리할거라는 어느 통신사 사례


준비 스크립트


CREATE TABLE 고객 AS
SELECT LEVEL 고객번호
     , LEVEL 납입자
  FROM DUAL
CONNECT BY LEVEL <= 1000000; --100만건

CREATE TABLE 청구 AS
SELECT B.고객번호
         , A.청구년월
         , 0 입금액
  FROM (SELECT '2005'||TO_CHAR( LEVEL , 'FM09' ) 청구년월
              FROM DUAL
            CONNECT BY  LEVEL <= 12 ) A
            , ( SELECT * FROM 고객 ) B --1200만건

CREATE INDEX 고객_INDEX01 ON 고객( 납입자 );

CREATE INDEX 청구_INDEX01 ON 청구( 고객번호, 청구년월 );

CREATE INDEX 청구_INDEX02 ON 청구( 청구년월, 고객번호  );

SQL> SELECT *
  2    FROM 고객
  3  WHERE 납입자 = 1000;

  고객번호     납입자
---------- ----------
      1000       1000




오티마이져가 UNNEST 선택함 ( 책하곤 틀리게 잘풀린다. ㅠㅠ )


SQL> UPDATE /*+ gather_plan_statistics */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000
  7                                       AND  Y.고객번호 = X.고객번호 );

1 행이 갱신되었습니다.

SQL> @XPLAN

-------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                        | 청구       |      1 |        |      0 |00:00:00.04 |      10 |      3 |       |       |          |
|   2 |   NESTED LOOPS                 |            |      1 |      1 |      1 |00:00:00.02 |       7 |      2 |       |       |          |
|   3 |    SORT UNIQUE                 |            |      1 |      1 |      1 |00:00:00.01 |       4 |      0 |  9216 |  9216 | 8192  (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |      0 |       |       |          |
|*  5 |      INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |      0 |       |       |          |
|*  6 |    INDEX RANGE SCAN            | 청구_INDEX0|      1 |      1 |      1 |00:00:00.02 |       3 |      2 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------------------------

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

   5 - access("납입자"=1000)
   6 - access("고객번호"="고객번호" AND "청구년월"='200503')


NO_UNNEST ( 청구_INDEX02 )


SQL> UPDATE /*+ gather_plan_statistics */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ NO_UNNEST */ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000
  7                                       AND  Y.고객번호 = X.고객번호 );

1 행이 갱신되었습니다.

SQL> @xplan

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:13.77 |    4003K|   3196 |
|*  2 |   INDEX RANGE SCAN            | 청구_INDEX0|      1 |  67149 |      1 |00:00:13.77 |    4003K|   3196 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| 고객       |   1000K|      1 |      1 |00:00:06.59 |    4000K|      0 |
|*  4 |     INDEX RANGE SCAN          | 고객_INDEX0|   1000K|      1 |   1000K|00:00:04.15 |    3000K|      0 |
---------------------------------------------------------------------------------------------------------------

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

   2 - access("청구년월"='200503')
       filter( IS NOT NULL)
   3 - filter("고객번호"=:B1)
   4 - access("납입자"=1000)



NO_UNNEST ( 청구_INDEX01 )


SQL> UPDATE /*+ gather_plan_statistics */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ NO_UNNEST */ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000
  7                                       AND  Y.고객번호 = X.고객번호 );

1 행이 갱신되었습니다.

SQL> @XPLAN

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:17.11 |    4031K|  31441 |
|*  2 |   FILTER                      |            |      1 |        |      1 |00:00:17.11 |    4031K|  31441 |
|*  3 |    TABLE ACCESS FULL          | 청구       |      1 |    575K|   1000K|00:00:01.30 |   31508 |  31441 |
|*  4 |    TABLE ACCESS BY INDEX ROWID| 고객       |   1000K|      1 |      1 |00:00:07.22 |    4000K|      0 |
|*  5 |     INDEX RANGE SCAN          | 고객_INDEX0|   1000K|      1 |   1000K|00:00:04.46 |    3000K|      0 |
---------------------------------------------------------------------------------------------------------------

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

   2 - filter( IS NOT NULL)
   3 - filter("청구년월"='200503')
   4 - filter("고객번호"=:B1)
   5 - access("납입자"=1000)



불필요한 연결고리 제거


SQL> UPDATE /*+ gather_plan_statistics  */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ */ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                        | 청구       |      1 |        |      0 |00:00:00.01 |      10 |       |       |          |
|   2 |   NESTED LOOPS                 |            |      1 |      1 |      1 |00:00:00.01 |       7 |       |       |          |
|   3 |    SORT UNIQUE                 |            |      1 |      1 |      1 |00:00:00.01 |       4 |  9216 |  9216 | 8192  (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |       |       |          |
|*  5 |      INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |       |       |          |
|*  6 |    INDEX RANGE SCAN            | 청구_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------

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

   5 - access("납입자"=1000)
   6 - access("고객번호"="고객번호" AND "청구년월"='200503')



UNNEST 말고 다른효율적인 방법은 없는것인가??


NESTED LOOPS SEMI


SQL> UPDATE /*+ gather_plan_statistics INDEX( X  청구_INDEX01 )*/ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+  NL_SJ*/ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:24.92 |    3031K|  31246 |
|   2 |   NESTED LOOPS SEMI           |            |      1 |      1 |      1 |00:00:24.92 |    3031K|  31246 |
|*  3 |    TABLE ACCESS FULL          | 청구       |      1 |   1342K|   1000K|00:00:09.04 |   31508 |  31246 |
|*  4 |    TABLE ACCESS BY INDEX ROWID| 고객       |   1000K|      1 |      1 |00:00:08.62 |    3000K|      0 |
|*  5 |     INDEX RANGE SCAN          | 고객_INDEX0|   1000K|      1 |   1000K|00:00:03.62 |    2000K|      0 |
---------------------------------------------------------------------------------------------------------------

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

   3 - filter("청구년월"='200503')
   4 - filter("고객번호"="고객번호")
   5 - access("납입자"=1000)

-- 청구_INDEX02
SQL> UPDATE /*+ gather_plan_statistics  INDEX( X  청구_INDEX02 )*/ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+  NL_SJ*/ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL>
SQL> @XPLAN

------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:09.18 |    3003K|
|   2 |   NESTED LOOPS SEMI           |            |      1 |      1 |      1 |00:00:09.18 |    3003K|
|*  3 |    INDEX RANGE SCAN           | 청구_INDEX0|      1 |   1342K|   1000K|00:00:00.01 |    3207 |
|*  4 |    TABLE ACCESS BY INDEX ROWID| 고객       |   1000K|      1 |      1 |00:00:08.27 |    3000K|
|*  5 |     INDEX RANGE SCAN          | 고객_INDEX0|   1000K|      1 |   1000K|00:00:03.38 |    2000K|
------------------------------------------------------------------------------------------------------

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

   3 - access("청구년월"='200503')
   4 - filter("고객번호"="고객번호")
   5 - access("납입자"=1000)    




그럼 청구_INDEX02을 사용해서 HASH JOIN SEMI로 풀면.? ( HASH JOIN SEMI 를 원했는데 오티마이져가 HASH JOIN RIGHT SEMI로 풀었다 )


SQL> UPDATE /*+ gather_plan_statistics  INDEX( X  청구_INDEX02 )*/ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+  HASH_SJ*/ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:00.26 |    3214 |       |       |          |
|*  2 |   HASH JOIN RIGHT SEMI        |            |      1 |      1 |      1 |00:00:00.26 |    3211 |  1035K|  1035K|  324K (0)| <--
|   3 |    TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |       |       |          |
|*  4 |     INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |       |       |          |
|*  5 |    INDEX RANGE SCAN           | 청구_INDEX0|      1 |   1342K|   1000K|00:00:00.01 |    3207 |       |       |          |
---------------------------------------------------------------------------------------------------------------------------------

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

   2 - access("고객번호"="고객번호")
   4 - access("납입자"=1000)
   5 - access("청구년월"='200503')



그럼 청구_INDEX02을 사용해서 HASH JOIN SEMI로 풀면.?


SQL> UPDATE /*+ gather_plan_statistics  INDEX( X  청구_INDEX02 ) LEADING( X) */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+  HASH_SJ*/ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

-------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem | Used-Tmp|
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:05.95 |    3212 |   3503 |   3503 |       |       |          |         |
|*  2 |   HASH JOIN SEMI              |            |      1 |      1 |      1 |00:00:05.95 |    3211 |   3503 |   3503 |    50M|  5133K|   69M (0)|   36864 | <--
|*  3 |    INDEX RANGE SCAN           | 청구_INDEX0|      1 |   1342K|   1000K|00:00:00.01 |    3207 |      0 |      0 |       |       |          |         |
|   4 |    TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |      0 |      0 |       |       |          |         |
|*  5 |     INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |      0 |      0 |       |       |          |         |
-------------------------------------------------------------------------------------------------------------------------------------------------------------

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

   2 - access("고객번호"="고객번호")
   3 - access("청구년월"='200503')
   5 - access("납입자"=1000)




그럼 청구_INDEX02을 사용해서 MERGE JOIN SEMI로 풀면.?


SQL> UPDATE /*+ gather_plan_statistics  INDEX( X  청구_INDEX02 )*/ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+  MERGE_SJ*/ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

Plan hash value: 2507689243

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                        | 청구       |      1 |        |      0 |00:00:00.01 |      11 |       |       |          |
|   2 |   MERGE JOIN SEMI              |            |      1 |      1 |      1 |00:00:00.01 |      10 |       |       |          |
|*  3 |    INDEX RANGE SCAN            | 청구_INDEX0|      1 |   1342K|   1001 |00:00:00.01 |       6 |       |       |          |
|*  4 |    SORT UNIQUE                 |            |   1001 |      1 |      1 |00:00:00.01 |       4 | 73728 | 73728 |          |
|   5 |     TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |       |       |          |
|*  6 |      INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------

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

   3 - access("청구년월"='200503')
   4 - access("고객번호"="고객번호")
       filter("고객번호"="고객번호")
   6 - access("납입자"=1000)



그럼 청구_INDEX02을 사용해서 고객 테이블을 선두테이블로 먼저 드라이빙 해서 MERGE JOIN SEMI로 풀면.?( 별짓 다해두 안됨 )


-- 1
SQL> UPDATE /*+ gather_plan_statistics  SWAP_JOIN_INPUTS(@SUB Y) INDEX( X@MAIN  청구_INDEX02 ) MERGE_SJ( @SUB )  QB_NAME( MAIN ) */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+  QB_NAME( SUB )*/ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000 );

1 행이 갱신되었습니다.

SQL> @XPLAN

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                        | 청구       |      1 |        |      0 |00:00:00.01 |      11 |       |       |          |
|   2 |   MERGE JOIN SEMI              |            |      1 |      1 |      1 |00:00:00.01 |      10 |       |       |          |
|*  3 |    INDEX RANGE SCAN            | 청구_INDEX0|      1 |   1342K|   1001 |00:00:00.01 |       6 |       |       |          |
|*  4 |    SORT UNIQUE                 |            |   1001 |      1 |      1 |00:00:00.01 |       4 | 73728 | 73728 |          |
|   5 |     TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |       |       |          |
|*  6 |      INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------

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

   3 - access("청구년월"='200503')
   4 - access("고객번호"="고객번호")
       filter("고객번호"="고객번호")
   6 - access("납입자"=1000)

-- 2
SQL> UPDATE /*+ gather_plan_statistics  SWAP_JOIN_INPUTS(@SUB Y) INDEX( X@MAIN  청구_INDEX02 ) HASH_SJ( @SUB )  QB_NAME( MAIN ) */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+  QB_NAME( SUB )*/ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000 );

1 행이 갱신되었습니다.

SQL> @XPLAN

------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:01.43 |    3212 |      5 |       |       |          |
|*  2 |   HASH JOIN RIGHT SEMI        |            |      1 |      1 |      1 |00:00:01.43 |    3211 |      5 |  1035K|  1035K|  310K (0)|
|   3 |    TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |      0 |       |       |          |
|*  4 |     INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |      0 |       |       |          |
|*  5 |    INDEX RANGE SCAN           | 청구_INDEX0|      1 |   1342K|   1000K|00:00:00.01 |    3207 |      5 |       |       |          |
------------------------------------------------------------------------------------------------------------------------------------------

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

   2 - access("고객번호"="고객번호")
   4 - access("납입자"=1000)
   5 - access("청구년월"='200503')
 



번외편 - SEMIJOIN_DRIVER( 안풀림 )


SQL> UPDATE /*+ gather_plan_statistics  SEMIJOIN_DRIVER(@SUB )  */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ QB_NAME( SUB ) */ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                        | 청구       |      1 |        |      0 |00:00:00.91 |      10 |       |       |          |
|   2 |   NESTED LOOPS                 |            |      1 |      1 |      1 |00:00:00.90 |       7 |       |       |          |
|   3 |    SORT UNIQUE                 |            |      1 |      1 |      1 |00:00:00.86 |       4 |  9216 |  9216 | 8192  (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.86 |       4 |       |       |          |
|*  5 |      INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |       |       |          |
|*  6 |    INDEX RANGE SCAN            | 청구_INDEX0|      1 |      1 |      1 |00:00:00.04 |       3 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------

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

   5 - access("납입자"=1000)
   6 - access("고객번호"="고객번호" AND "청구년월"='200503')


번외편 - SWAP_JOIN_INPUTS

SQL> UPDATE /*+ gather_plan_statistics  SWAP_JOIN_INPUTS(@SUB Y) QB_NAME( MAIN ) */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ QB_NAME( SUB ) */ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

-------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                        | 청구       |      1 |        |      0 |00:00:00.14 |      10 |      3 |       |       |          |
|   2 |   NESTED LOOPS                 |            |      1 |      1 |      1 |00:00:00.12 |       7 |      2 |       |       |          |
|   3 |    SORT UNIQUE                 |            |      1 |      1 |      1 |00:00:00.01 |       4 |      0 |  9216 |  9216 | 8192  (0)|
|   4 |     TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |      0 |       |       |          |
|*  5 |      INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |      0 |       |       |          |
|*  6 |    INDEX RANGE SCAN            | 청구_INDEX0|      1 |      1 |      1 |00:00:00.12 |       3 |      2 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------------------------

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

   5 - access("납입자"=1000)
   6 - access("고객번호"="고객번호" AND "청구년월"='200503')

--음.. 좀더 공격적인 힌트
SQL> UPDATE /*+ gather_plan_statistics  SWAP_JOIN_INPUTS( @SUB Y )  LEADING( @SUB Y ) HASH_SJ(  @SUB )  INDEX( X 청구_INDEX02 )   */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ QB_NAME( SUB ) */  ( 고객번호 )
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

---------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:01.24 |   36800 |       |       |          |
|*  2 |   HASH JOIN RIGHT SEMI        |            |      1 |      1 |      1 |00:00:01.24 |   36799 |  1035K|  1035K|  340K (0)|
|   3 |    TABLE ACCESS BY INDEX ROWID| 고객       |      1 |      1 |      1 |00:00:00.01 |       4 |       |       |          |
|*  4 |     INDEX RANGE SCAN          | 고객_INDEX0|      1 |      1 |      1 |00:00:00.01 |       3 |       |       |          |
|*  5 |    INDEX FULL SCAN            | 청구_INDEX0|      1 |    575K|   1000K|00:00:01.00 |   36795 |       |       |          |
---------------------------------------------------------------------------------------------------------------------------------

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

   2 - access("고객번호"="고객번호")
   4 - access("납입자"=1000)
   5 - access("청구년월"='200503')
       filter("청구년월"='200503')



Filter 서브쿼리


SQL> UPDATE /*+ gather_plan_statistics LEADING( @SUB Y )  NO_UNNEST(  @SUB )  INDEX( X 청구_INDEX01 )   */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ QB_NAME( SUB ) */ 고객번호
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:24.74 |    4038K|  38444 |
|*  2 |   INDEX FULL SCAN             | 청구_INDEX0|      1 |  28751 |      1 |00:00:24.74 |    4038K|  38444 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| 고객       |   1000K|      1 |      1 |00:00:07.15 |    4000K|      0 |
|*  4 |     INDEX RANGE SCAN          | 고객_INDEX0|   1000K|      1 |   1000K|00:00:04.40 |    3000K|      0 |
---------------------------------------------------------------------------------------------------------------

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

   2 - access("청구년월"='200503')
       filter(("청구년월"='200503' AND  IS NOT NULL))
   3 - filter("고객번호"=:B1)
   4 - access("납입자"=1000)



INDEX FULL SCAN 다른 방법은 없는것인가?? 유니크 인덱스가 없어서 그런거 같다.. 생성



DROP INDEX 청구_INDEX01

CREATE UNIQUE INDEX 청구_INDEX02 ON 청구( 고객번호, 청구년월 );

SQL> UPDATE /*+ gather_plan_statistics   LEADING( @SUB Y )  NO_UNNEST(  @SUB )  INDEX( X 청구_INDEX02 )   */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ QB_NAME( SUB ) */  ( 고객번호 )
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

-- 아니군요 ㅠ
---------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:09.54 |    4036K|     47 |
|*  2 |   INDEX FULL SCAN             | 청구_INDEX0|      1 |  28751 |      1 |00:00:09.54 |    4036K|     47 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| 고객       |   1000K|      1 |      1 |00:00:06.99 |    4000K|      0 |
|*  4 |     INDEX RANGE SCAN          | 고객_INDEX0|   1000K|      1 |   1000K|00:00:04.28 |    3000K|      0 |
---------------------------------------------------------------------------------------------------------------

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

   2 - access("청구년월"='200503')
       filter(("청구년월"='200503' AND  IS NOT NULL))
   3 - filter("고객번호"=:B1)
   4 - access("납입자"=1000)




선두 컬럼이 알수 없으니 풀스캔으로 타는것 같다. 청구_INDEX03 ON 청구( 청구년월, 고객번호 ) 확인자( Early Filter 서브쿼리 )


CREATE INDEX 청구_INDEX03 ON 청구( 청구년월, 고객번호 );

SQL> UPDATE /*+ gather_plan_statistics  LEADING( @SUB Y ) NO_UNNEST( @SUB  ) INDEX( X 청구_INDEX03)   */ 청구 X
  2       SET 입금액 = NVL( 입금액, 0 ) + 50000
  3  WHERE 청구년월  = '200503'
  4      AND 고객번호 IN (SELECT /*+ QB_NAME( SUB ) */  ( 고객번호 )
  5                                    FROM 고객 Y
  6                                   WHERE 납입자 = 1000);

1 행이 갱신되었습니다.

SQL> @XPLAN

Plan hash value: 483576752

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------
|   1 |  UPDATE                       | 청구       |      1 |        |      0 |00:00:17.10 |    4003K|   3059 |
|*  2 |   INDEX RANGE SCAN            | 청구_INDEX0|      1 |  28751 |      1 |00:00:17.10 |    4003K|   3059 |
|*  3 |    TABLE ACCESS BY INDEX ROWID| 고객       |   1000K|      1 |      1 |00:00:06.98 |    4000K|      0 |
|*  4 |     INDEX RANGE SCAN          | 고객_INDEX0|   1000K|      1 |   1000K|00:00:04.27 |    3000K|      0 |
---------------------------------------------------------------------------------------------------------------

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

   2 - access("청구년월"='200503')
       filter( IS NOT NULL)
   3 - filter("고객번호"=:B1)
   4 - access("납입자"=1000)


"구루비 데이터베이스 스터디모임" 에서 2011년에 "새로쓴 대용량 데이터베이스 솔루션1" 도서를 스터디하면서 정리한 내용 입니다.

- 강좌 URL : http://www.gurubee.net/lecture/4452

- 구루비 강좌는 개인의 학습용으로만 사용 할 수 있으며, 다른 웹 페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^

- 구루비 강좌는 서비스 제공을 위한 목적이나, 학원 홍보, 수익을 얻기 위한 용도로 사용 할 수 없습니다.

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입