2013년 8월 3일 토요일

[Oracle SQL Explain Plan]ORACLE HINT 강좌 실행계획 SQL 연산(HASH ANTI-JOIN)

실행계획 SQL 연산(HASH ANTI-JOIN

구로디지털 오엔제이프로그래밍실무교육센터

ANTI 조인은 조인의 대상이 되는 테이블과 일치하지 않는 데이터를 추출하는 연산 입니다. SQL연산에서 NOT IN, NOT EXISTS, MINUS등이 해당되며 이러한 안티 조인은 MERGE ANTI-JOIN or HASH ANTI_JOIN으로 풀리도록 할 수 있습니다.

아래의 Query는 동일한 의미를 가지는 질의 입니다. 확인해 보세요~

create table myemp1
(empno number not null primary key,
 ename varchar2(100),
 deptno number,
 addr   varchar2(100),
 sal    number
 )

-- 실습을 위해 myemp1 1000만건 만들자.
DECLARE
          v_c NUMBER := 1;
BEGIN

          WHILE (v_c <= 10000000) LOOP
                insert into myemp1 values ( v_c, '홍길동'||v_c, mod(v_c, 5), '서울'||v_c, mod(v_c, 1000000));
                v_c := v_c + 1;
                insert into myemp1 values ( v_c, '다길동'||v_c, mod(v_c, 5), '부산'||v_c, mod(v_c, 1000000));
                v_c := v_c + 1;
                insert into myemp1 values ( v_c, '나길동'||v_c, mod(v_c, 5), '대구'||v_c, mod(v_c, 1000000));
                v_c := v_c + 1;
                insert into myemp1 values ( v_c, '나길동'||v_c, mod(v_c, 5), '광주'||v_c, mod(v_c, 1000000));
                v_c := v_c + 1;
          END LOOP;
          commit;
END;


create table myemp1_old
as select * from myemp1 where rownum < 1000000


  

 -- 통계정보 셍성
    exec DBMS_STATS.GATHER_TABLE_STATS(USER, 'MYDEPT1_OLD')
   
    exec DBMS_STATS.GATHER_TABLE_STATS(USER, 'MYDEPT1')




별다른 인덱스가 없는 상태에서 실행해 보자. 37초 걸린다.
오라클 11g에서 기본적으로 merge anti joinm으로 실행계획을 잡는다.


SQL> select count(e1.ename) 
  2   from myemp1 e1
  3   where (ename, sal) not in (select ename, sal
  4                                from myemp1_old e2);

COUNT(E1.ENAME)--28
-------------------
            9000001

   : 00:00:37.87

Execution Plan
----------------------------------------------------------
|   0 | SELECT STATEMENT     |            |     1 |    82 |       | 90492  
|   1 |  SORT AGGREGATE      |            |     1 |    82 |       |           
|   2 |   MERGE JOIN ANTI NA |            |    10M|   782M|       | 90492  
|   3 |    SORT JOIN         |            |    10M|   162M|   536M| 72263  
|   4 |     TABLE ACCESS FULL| MYEMP1     |    10M|   162M|       | 16961  
|*  5 |    SORT UNIQUE       |            |  1070K|    66M|   156M| 18229  
|   6 |     TABLE ACCESS FULL| MYEMP1_OLD |  1070K|    66M|       | 




이번에는 HASH ANTI JOIN으로 힌트를 주고 실행하자. ANTI JOIN 일때는 where절의 not in 출현 컬럼에 대해 is not null 조건을 주도록 하자.

SQL> select
           count(e1.ename)
  from myemp1 e1
  where (ename, sal) not in (select
                                    ename, sal
                               from myemp1_old e2
                               where ename is not null
                                 and sal is not null)
 and ename is not null
  and    sal is not null
 /

실행해 보면 수행시간이 절반 이상으로 준다. MERGE ANTI JOIN 보다 HASH ANTI JOIN 성능이 낫다.

COUNT(E1.ENAME)
---------------
        9000001

   : 00:00:14.92

Execution Plan
----------------------------------------------------------
|   0 | SELECT STATEMENT      |            |     1 |    82 |       | 36297  
|   1 |  SORT AGGREGATE       |            |     1 |    82 |       |
|*  2 |   HASH JOIN RIGHT ANTI|            |    10M|   782M|    78M| 36297  
|*  3 |    TABLE ACCESS FULL  | MYEMP1_OLD |  1070K|    66M|       | 
|*  4 |    TABLE ACCESS FULL  | MYEMP1     |    10M|   162M|       |



SQL> SELECT count(ename)
  2                FROM   MYEMP1 E
  3  WHERE  NOT EXISTS (SELECT  1
  4                     FROM MYEMP1_OLD EO
  5                     WHERE  EO.ENAME = E.ENAME
  6                     AND     EO.SAL    = E.SAL)
  7  and ename is not null
  8  and   sal is not null;

COUNT(ENAME)
------------
     9000001

   : 00:00:11.56

Execution Plan
----------------------------------------------------------
|   0 | SELECT STATEMENT      |            |     1 |    82 |       | 36295  
|   1 |  SORT AGGREGATE       |            |     1 |    82 |       |
|*  2 |   HASH JOIN RIGHT ANTI|            |    10M|   782M|    78M| 36295  
|   3 |    TABLE ACCESS FULL  | MYEMP1_OLD |  1070K|    66M|       | 
|*  4 |    TABLE ACCESS FULL  | MYEMP1     |    10M|   162M|       |



이번에는 HASH_AJ 힌트를 사용해 보자.
수행 시간은 대략 비슷하다.



SQL> SELECT count(ename)
  2                FROM   MYEMP1 E
  3  WHERE  NOT EXISTS (SELECT  /*+ hash_aj */1
  4                     FROM MYEMP1_OLD EO
  5                     WHERE  EO.ENAME = E.ENAME
  6                     AND     EO.SAL    = E.SAL)
  7  and ename is not null
  8  and   sal is not null;

COUNT(ENAME)
------------
     9000001

   : 00:00:11.00

Execution Plan
----------------------------------------------------------
|   0 | SELECT STATEMENT      |            |     1 |    82 |       | 36295  
|   1 |  SORT AGGREGATE       |            |     1 |    82 |       |
|*  2 |   HASH JOIN RIGHT ANTI|            |    10M|   782M|    78M| 36295  
|   3 |    TABLE ACCESS FULL  | MYEMP1_OLD |  1070K|    66M|       | 
|*  4 |    TABLE ACCESS FULL  | MYEMP1     |    10M|   162M|       |



이번에는 MINUS로 풀어 보자.

SQL> with a as (
  2      select  ename, sal
  3      from myemp1
  4      minus
  5      select  ename, sal
  6      from myemp1_old
  7   )
  8  select count(ename) from a  ;

COUNT(ENAME)
------------
     9000001

   : 00:00:30.48

Execution Plan
----------------------------------------------------------
|   0 | SELECT STATEMENT      |            |     1 |    52 |       | 90492  
|   1 |  SORT AGGREGATE       |            |     1 |    52 |       |
|   2 |   VIEW                |            |    10M|   495M|       | 90492  
|   3 |    MINUS              |            |       |       |       |
|   4 |     SORT UNIQUE       |            |    10M|   162M|   268M| 72263  
|   5 |      TABLE ACCESS FULL| MYEMP1     |    10M|   162M|       |
|   6 |     SORT UNIQUE       |            |  1070K|    66M|    78M| 18229  
|   7 |      TABLE ACCESS FULL| MYEMP1_OLD |  1070K|    66M|       | 


HASH ANTI JOIN으로 풀 수 있는 것은 NOT IN을 포함하고 있는 첫 번째 질의에서 가장 좋은 성능을 보이며 NOT IN의 비교 대상이 되는 컬럼은 NOT NULL로 서브쿼리까지 명시해 주어야 합니다. 물론 HASH_AJ 라는 힌트 구문도 사용해야 하구요~

HSH ANTI JOIN으로 풀 경우 성능이 향상되므로 위 문장과 같이 한 테이블에 존재하지 않는 로우만 추출하는 경우엔 HASH ANTI JOIN  되도록 힌트를 사용하는 것이 유리합니다.


이번에는 조금 더 개선을 해서 EMPTEST TABLE  EMPNO, ENAME, SAL 컬럼으로 비트맵 인덱스를 구성해서 쿼리를 해 보자.

create bitmap index idx_bitmap_empno_ename_sal on emptiest (empno, ename, sal)

select   /*+ index(idx_bitmap_empno_ename_sal e1) */
          count(e1.ename)  
 from emptest e1
 where (ename, sal) not in (select
                                   ename, sal
                              from emptest_old e2
                              where ename is not null
                                and sal is not null)
 and    ename is not null
 and    sal is not null


댓글 없음:

댓글 쓰기