2013년 8월 3일 토요일

ORACLE Tuning, [oracle hint]조인 방법 변경(HASH_AJ)

[Hint]조인 방법 변경(HASH_AJ)
 
구로디지털 오엔제이프로그래밍실무교육센터
 
ANTI 조인은 테이블의 레코드를 추출하는 경우 조인의 대상이 되는 테이블과 일치하지 않는 데이터를 추출하는 연산 입니다. SQL연산에서 NOT IN, NOT EXISTS, MINUS등이 해당되는데 이러한 안티 조인은 MERGE ANTI-JOIN or HASH ANTI_JOIN으로 풀리도록 할 수 있는데HASH_AJ에 대해 살펴보도록 하죠,,.,
 
주로 NOT IN등의 SQL문에 이용되며 힌트 구문은 다음과 같이 서브 쿼리에 명시해야 하며 서브 쿼리의 WHERE절에 NOT NULL 조건도 명시해줘야 합니다.
 
[형식]
 
SQL> SELECT ENAME, SAL
      FROM  EMP
      WHERE EMPNO IS NOT NULL
      AND    ENAME IS NOT NULL
      AND    (EMPNO, ENAME) NOT IN
                                  (SELECT
                                           EMPNO, ENAME
                                   FROM EMP_BAK
                                   WHERE EMPNO IS NOT NULL
                                   AND    ENAME IS NOT NULL)
 
Execution Plan
-----------------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE
    HASH JOIN(ANTI)
      TABLE ACCESS (FULL) OF EMP
 TABLE ACCESS (FULL) OF EMP_BAK
 
 
============================================================
[아래의 예는 실행계획 SQL 연산(HASH ANTI-JOIN) 강좌의 일부 내용 입니다]
=============================================================
 
아래의 Query는 동일한 의미를 가지는 질의 입니다확인해 보세요~

SQL> SELECT EMPNO,
              
 ENAME,               SAL      FROM   EMP      WHERE  (EMPNO, ENAME, SAL) NOT IN (SELECT EMPNO,                                                         ENAME,                                                         SAL                                                FROM   EMP_OLD);

Execution Plan
--------------------------------------------------------
0
  
 SELECT STATEMENT Optimizer=CHOOSE
1
   0  
 FILTER
2
   1     TABLE ACCESS (FULL) OF EMP

3
   1     TABLE ACCESS (FULL) OF EMP_OLD


SQL> SELECT EMPNO,
               ENAME,               SAL      FROM   EMP E      WHERE  NOT EXISTS (SELECT 1
FROM EMP_OLD EO
WHERE
  
EO.EMPNO = E.EMPNO
AND
    
 EO.ENAME = E.ENAME
AND
     EO.SAL    
= E.SAL);

Execution Plan
-------------------------------------------------------------
SELECT STATEMENT Optimizer=CHOOSE
1
   0  
 FILTER
2
   1     TABLE ACCESS (FULL) OF EMP

3
   1     TABLE ACCESS (FULL) OF EMP_OLD


SQL> SELECT EMPNO, ENAME, SAL
  FROM   EMP      MINUS
SELECT EMPNO, ENAME, SAL
  FROM  
 EMP_OLD


Execution Plan
-------------------------------------------------------------
0
  
 SELECT STATEMENT Optimizer=CHOOSE
1
   0  
 MINUS
2
   1    
 SORT (UNIQUE)
3
   2        TABLE ACCESS (FULL) OF EMP

4
   1     SORT (UNIQUE)
5
   2        TABLE ACCESS (FULL) OF EMP_OLD


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


SQL> SELECT EMPNO,
              
 ENAME,               SAL      FROM   EMP      WHERE  EMPNO IS NOT NULL      AND     ENAME IS NOT NULL      AND     SAL    IS NOT NULL
AND
    
(EMPNO, ENAME, SAL)
NOT IN (SELECT
EMPNO,
                                    
ENAME,                                    SAL                            FROM  EMP_OLD
WHERE
  
EMPNO IS NOT NULL      AND     ENAME IS NOT NULL      AND     SAL    IS NOT NULL);

Execution Plan
--------------------------------------------------------
0
  
 SELECT STATEMENT Optimizer=CHOOSE
1
   0  
 HASH JOIN(ANTI)
2
   1     TABLE ACCESS (FULL) OF EMP

3
   1     TABLE ACCESS (FULL) OF EMP_OLD


HSH ANTI JOIN
으로 풀 경우 성능이 향상되므로 위 문장과 같이 한 테이블에 존재하지 않는 로우만 추출하는 경우엔 HASH ANTI JOIN  되도록 힌트를 사용하는 것이 유리합니다.
 
 
 
[실습]
 
-      실습을 위한 예제 테이블 및 데이터는 아래 링크에서 확인 바랍니다.
 
myemp1 : 1000만건
myemp1_old : 100만건
mydept : 5
 
테스트환경 : oracle 11g
 
 
 
 
 
 
아래 세개 SQL문장은 같은 결과를 만들어 내는 동일한 SQL문이다.
 
인덱스 먼저 만들자.
 
SQL>create index idx_myemp1_old_ename_sal on myemp1_old(ename, sal)
SQL>create index idx_myemp1_ename_sal on myemp1(ename, sal)
 
먼저 not in을 사용하여 질의해 보자. (엄청 느리다 인덱스 있어도 느리지만 정말 느리다)
 
첫번째 NOT IN을 이용한 방법
 
SQL> select empno,
  2         ename
  3  from   myemp1 e1
  4  where  (ename, sal) not in (select ename, sal
  5                                from myemp1_old e2
  6                               where e1.ename = e2.ename
  7                                 and e1.sal = e2.sal);
 
9000001 개의 행이 선택되었습니다.
 
   : 00:01:35.99
 
-----------------------------------------------------------------------------------------------
| Id  | Operation          | Name                     | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                          |    10M|   219M|    30M  (1)|100:03:28 |
|*  1 |  FILTER            |                          |       |       |            |          |
|   2 |   TABLE ACCESS FULL| MYEMP1                   |    10M|   219M| 16971   (1)| 00:03:24 |
|*  3 |   INDEX RANGE SCAN | IDX_MYEMP1_OLD_ENAME_SAL |     1 |    18 |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
 
 
두번째 NOT EXISTS를 이용한 방법
 
 
SQL> select empno,
  2         ename
  3  from   myemp1 e1
  4  where  not exists (select 1
  5                       from myemp1_old e2
  6                      where e1.ename = e2.ename
  7                        and e1.sal = e2.sal);
 
9000001 개의 행이 선택되었습니다.
 
   : 00:00:52.96
-------------------------------------------------------------------------------------------------| Id  | Operation             | Name                     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time  |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                          |  9000K|   351M|       | 36134   (1)| 00:07:1
|*  1 |  HASH JOIN RIGHT ANTI |                          |  9000K|   351M|    28M| 36134   (1)| 00:07:1
|   2 |   INDEX FAST FULL SCAN| IDX_MYEMP1_OLD_ENAME_SAL |   999K|    17M|       |  1100   (1)| 00:00:1
|   3 |   TABLE ACCESS FULL   | MYEMP1                   |    10M|   219M|       | 16961   (1)| 00:03:2
-------------------------------------------------------------------------------------------------
 
 
세번째 MINUS를 이용한 방법
 
 
SQL> select ename, sal from myemp1
  2  minus
  3  select ename, sal from myemp1_old;
 
9000001 개의 행이 선택되었습니다.
 
   : 00:01:22.86
 
------------------------------------------------------------------------------------------
| Id  | Operation           | Name       | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |            |    10M|   188M|       | 81447  (10)| 00:16:18 |
|   1 |  MINUS              |            |       |       |       |            |          |
|   2 |   SORT UNIQUE       |            |    10M|   171M|   268M| 74150   (1)| 00:14:50 |
|   3 |    TABLE ACCESS FULL| MYEMP1     |    10M|   171M|       | 16961   (1)| 00:03:24 |
|   4 |   SORT UNIQUE       |            |   999K|    17M|    26M|  7297   (1)| 00:01:28 |
|   5 |    TABLE ACCESS FULL| MYEMP1_OLD |   999K|    17M|       |  1582   (1)| 00:00:19 |
------------------------------------------------------------------------------------------
 
 
세가지 방법 중에서는 두번째 NOT EXISTS가 가장 빠른 결과를 보여준다아마도myemp1_old ename, sal 인덱스 덕분인 것 같다.
 
이번에는 첫번째 not in 예문을 해시 안티 조인(hash_aj)로 바꾸어 보자안쪽과 바깥쪽where절에 비교 대상 컬럼에 대해 is not null 비교를 해야 한다.
 
조금 빨라졌다.
 
SQL> select empno,
  2         ename
  3  from   myemp1 e1
  4  where  (ename, sal) not in (select
  5                                     ename, sal
  6                                from myemp1_old e2
  7                               where e1.ename = e2.ename
  8                                 and e1.sal = e2.sal
  9                                 and ename is not null
 10                                 and sal   is not null)
 11  and    ename is not null
 12  and    sal   is not null;
 
9000001 개의 행이 선택되었습니다.
 
   : 00:00:53.32
 
-------------------------------------------------------------------------------------------------
| Id  | Operation             | Name                     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time  |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                          |  9000K|   351M|       | 36156   (1)| 00:07:14 |
|*  1 |  HASH JOIN RIGHT ANTI |                          |  9000K|   351M|    28M| 36156   (1)| 00:07:14 |
|*  2 |   INDEX FAST FULL SCAN| IDX_MYEMP1_OLD_ENAME_SAL |   999K|    17M|       |  1102   (1)| 00:00:14
|*  3 |   TABLE ACCESS FULL   | MYEMP1                   |    10M|   219M|       | 16981   (1)| 00:03:24 |
-------------------------------------------------------------------------------------------------
 
이번에는 merge anti join(merge_aj)로 바꾸어서 실행 해보자.
 
해시 안티 조인 보다는 조금 느리다.
 
SQL> select empno,
  2         ename
  3  from   myemp1 e1
  4  where  (ename, sal) not in (select
  5                                     ename, sal
  6                                from myemp1_old e2
  7                               where e1.ename = e2.ename
  8                                 and e1.sal = e2.sal
  9                                 and ename is not null
 10                                 and sal   is not null)
 11  and    ename is not null
 12  and    sal   is not null;
 
9000001 개의 행이 선택되었습니다.
 
   : 00:01:15.66
 
-------------------------------------------------------------------------------------------------
| Id  | Operation              | Name                     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                          |  9000K|   351M|       | 92322   (1)| 00:18:28 |
|   1 |  MERGE JOIN ANTI       |                          |  9000K|   351M|       | 92322   (1)| 00:18:28 |
|   2 |   SORT JOIN            |                          |    10M|   219M|   691M| 85505   (1)| 00:17:07 |
|*  3 |    TABLE ACCESS FULL   | MYEMP1                   |    10M|   219M|       | 16981   (1)| 00:03:24 |
|*  4 |   SORT UNIQUE          |                          |   999K|    17M|    53M|  6817   (1)| 00:01:22 |
|*  5 |    INDEX FAST FULL SCAN| IDX_MYEMP1_OLD_ENAME_SAL |   999K|    17M|       |  1102   (1)| 00:00:14 

댓글 없음:

댓글 쓰기