본문 바로가기

04번. IT 힌트얻기/▶ DB Tuning

[Tuning]결합인덱스 문제 관련

조인 시 사용된 결합인덱스의 문제

▶ Optimizer_mode = Rule Based Optimizer

▶ 인덱스 정보
EC_COURSE_CD : EC_COURSE_CD_PK : DIM_COURSE_KEY
EC_USER_CNT  : EC_USER_CNT_PK  : DIM_TIME_KEY + DIM_COURSE_KEY + DIM_GEN_KEY

▶ Before Tuning (Original Source)
SELECT A.DIM_COURSE_NAME,B.DIM_TIME_KEY,SUM(B.USER_CNT)
  FROM EC_COURSE_CD A, EC_USER_CNT B
 WHERE A.DIM_COURSE_KEY =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
   AND B.DIM_TIME_KEY LIKE '2001%'
 GROUP BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.01          2          2          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.14       0.18        147      39172          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.15       0.20        149      39174          0         109

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    NESTED LOOPS
  19455     TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'            (B)
  19455      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)
    952     TABLE ACCESS (BY INDEX ROWID) OF 'EC_COURSE_CD'           (A)
  19455      INDEX (UNIQUE SCAN) OF 'EC_COURSE_CD_PK' (UNIQUE)

  B - Index Range Scan
  A - DIM_BIG_KEY가 인덱스가 존재하지 않기 때문에 Full Table Scan을 할 것이다.

  ---> 그래서 우선 순위 측면에서 B가 먼저 선택이 된 것이다.
  ---> 조인 순서는 바꿀수 없다면 조인 횟수라도 줄여보자..

▶ View 를 통한 변형 (VIEW MERGE 현상 발생)
튜닝시 뷰의 사용 목적

1. 테이블 사이의 조인 회수 개선 (감소)
2. 조인 순서 제어
3. 함수 사용회수(빈도수)를 개선 (감소)
4. update 작업의 최적화


SELECT A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       B.USER_CNT
  FROM EC_COURSE_CD A,
      (SELECT DIM_TIME_KEY,DIM_COURSE_KEY,
              SUM(USER_CNT) USER_CNT
         FROM EC_USER_CNT
        WHERE DIM_TIME_KEY LIKE '2001%'
        GROUP BY DIM_TIME_KEY,DIM_COURSE_KEY) B
 WHERE A.DIM_COURSE_KEY =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
 ORDER BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
 
 call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.13       0.12          0      39172          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.14       0.12          0      39172          0         109
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    NESTED LOOPS
  19455     TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
  19455      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)
    952     TABLE ACCESS (BY INDEX ROWID) OF 'EC_COURSE_CD'
  19455      INDEX (UNIQUE SCAN) OF 'EC_COURSE_CD_PK' (UNIQUE)

위의 것과 비교해 보았을 때 별다른 차이가 없으며 view에 대한 것에 문제가 있다. 나타나지 않는다.
왜냐하면 view merge 때문에 그런 것이다.

▶ View 를 통한 변형 (VIEW MERGE 현상 발생 해소)
-- 9i 부터 group by 가 변형 예외 케이스에서 사라져서 발생한 것이다.
-- 해결 방법 : group by 뒤 order by 추가 하고 컬럼은 동일한 것 넣어준다.

SELECT A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       B.USER_CNT
  FROM EC_COURSE_CD A,
      (SELECT DIM_TIME_KEY,DIM_COURSE_KEY,
              SUM(USER_CNT) USER_CNT
         FROM EC_USER_CNT
        WHERE DIM_TIME_KEY LIKE '2001%'
        GROUP BY DIM_TIME_KEY,DIM_COURSE_KEY
        ORDER BY DIM_TIME_KEY,DIM_COURSE_KEY) B
 WHERE A.DIM_COURSE_KEY =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
 ORDER BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.06       0.05          0       6726          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.06       0.05          0       6726          0         109

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (ORDER BY)
    109    NESTED LOOPS
   3232     VIEW                      <-----------------------
   3232      SORT (GROUP BY)
  19455       TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
  19455        INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)
    109     TABLE ACCESS (BY INDEX ROWID) OF 'EC_COURSE_CD'
   3231      INDEX (UNIQUE SCAN) OF 'EC_COURSE_CD_PK' (UNIQUE)

▶ WITH ~ AS 구문을 활용한 변경 (여기서도 VIEW MERGE가 일어남을 확인해야 한다.)
 # VIEW MERGE가 되지 않는 경우

WITH B AS (SELECT DIM_TIME_KEY,DIM_COURSE_KEY,
              SUM(USER_CNT) USER_CNT
         FROM EC_USER_CNT
        WHERE DIM_TIME_KEY LIKE '2001%'
        GROUP BY DIM_TIME_KEY,DIM_COURSE_KEY
        ORDER BY DIM_TIME_KEY,DIM_COURSE_KEY)
SELECT A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       B.USER_CNT
  FROM EC_COURSE_CD A, B
 WHERE A.DIM_COURSE_KEY =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
 ORDER BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.06       0.05          0       6726          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.06       0.05          0       6726          0         109

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (ORDER BY)
    109    NESTED LOOPS
   3232     VIEW
   3232      SORT (GROUP BY)
  19455       TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
  19455        INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)
    109     TABLE ACCESS (BY INDEX ROWID) OF 'EC_COURSE_CD'
   3231      INDEX (UNIQUE SCAN) OF 'EC_COURSE_CD_PK' (UNIQUE)


▶ WITH ~ AS 구문을 활용한 변경 
 # VIEW MERGE가 된 경우

WITH B AS (SELECT DIM_TIME_KEY,DIM_COURSE_KEY,
              SUM(USER_CNT) USER_CNT
         FROM EC_USER_CNT
        WHERE DIM_TIME_KEY LIKE '2001%'
        GROUP BY DIM_TIME_KEY,DIM_COURSE_KEY)
SELECT A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       B.USER_CNT
  FROM EC_COURSE_CD A, B
 WHERE A.DIM_COURSE_KEY =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
 ORDER BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.13       0.12          0      39172          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.13       0.12          0      39172          0         109

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    NESTED LOOPS
  19455     TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
  19455      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)
    952     TABLE ACCESS (BY INDEX ROWID) OF 'EC_COURSE_CD'
  19454      INDEX (UNIQUE SCAN) OF 'EC_COURSE_CD_PK' (UNIQUE)

테이블 순서를 바꾸기...
FULL TABLE SCAN의 영향때문에 속도가 늦는지 알아보기 위해 인덱스 설정 후 진행한다.

인덱스 설정
SQL> CREATE INDEX EC_COURSE_CD_IDX01 ON EC_COURSE_CD(DIM_BIG_KEY)
  2  TABLESPACE TS_EC_IDX STORAGE (INITIAL 1M NEXT 1M);

hint 사용 (인덱스 전후 비교)

SELECT /*+ ORDERED USE_NL(A B) */
       A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       SUM(B.USER_CNT)
  FROM EC_COURSE_CD A, EC_USER_CNT B
 WHERE A.DIM_COURSE_KEY =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
   AND B.DIM_TIME_KEY LIKE '2001%'
 GROUP BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
 
(1) 인덱스 설정 전
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.25       0.24          6       3400          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.26       0.25          6       3400          0         109
(2) 인덱스 설정 후
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.25       0.24          6       3400          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.26       0.25          6       3400          0         109
(1)인덱스 설정 전
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      TABLE ACCESS (FULL) OF 'EC_COURSE_CD'
    952      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)
(2) 인덱스 설정 후
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      TABLE ACCESS (BY INDEX ROWID) OF 'EC_COURSE_CD'
    952       INDEX (RANGE SCAN) OF 'EC_COURSE_CD_IDX01' (NON-UNIQUE)
      0      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)

Suppressing 사용 (인덱스 사용 전후 비교)

SELECT A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       SUM(B.USER_CNT)
  FROM EC_COURSE_CD A, EC_USER_CNT B
 WHERE A.DIM_COURSE_KEY + 0 =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
   AND B.DIM_TIME_KEY LIKE '2001%'
 GROUP BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
(1)인덱스 설정 전
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.38       0.37          0       3400          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.38       0.37          0       3400          0         109
(2) 인덱스 설정 후
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.38       0.37          0       3400          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.38       0.37          0       3400          0         109
(1)인덱스 설정 전
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      TABLE ACCESS (FULL) OF 'EC_COURSE_CD'
    952      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)
   
(2) 인덱스 설정 후
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      TABLE ACCESS (BY INDEX ROWID) OF 'EC_COURSE_CD'
    952       INDEX (RANGE SCAN) OF 'EC_COURSE_CD_IDX01' (NON-UNIQUE)
      0      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)   

view 사용 (인덱스 사용 전후 비교)

(1)인덱스 설정 전
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.26       0.24          0       3400          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.26       0.24          0       3400          0         109
(2) 인덱스 설정 후
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.26       0.24          0       3400          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.26       0.24          0       3400          0         109

(1)인덱스 설정 전
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      VIEW
     51       COUNT
     51        TABLE ACCESS (FULL) OF 'EC_COURSE_CD'
    952      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)
(2) 인덱스 설정 후   
Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      VIEW
     51       COUNT
     51        TABLE ACCESS (BY INDEX ROWID) OF 'EC_COURSE_CD'
    952         INDEX (RANGE SCAN) OF 'EC_COURSE_CD_IDX01'
                    (NON-UNIQUE)
      0      INDEX (RANGE SCAN) OF 'EC_USER_CNT_PK' (UNIQUE)

full table scan에 따른 성능 저하는 문제가 아니다.....

설계상의 문제가 있는 것이다. 결합인덱스의 문제를 살펴보아야 한다.

생성한 인덱스 삭제하기

SQL> DROP INDEX EC_COURSE_CD_IDX01;
Index dropped.

설계상의 이슈를 해결하기 위해 새로운 인덱스를 생성한다.

CREATE INDEX EC_USER_CNT_IDX
ON EC_USER_CNT(DIM_COURSE_KEY, DIM_TIME_KEY)
TABLESPACE TS_EC_IDX
STORAGE (INITIAL 1M NEXT 1M PCTINCREASE 0)
UNRECOVERABLE;

▶ 인덱스 정보
EC_COURSE_CD : EC_COURSE_CD_PK : DIM_COURSE_KEY
(전) USER_CNT  : EC_USER_CNT_PK  : DIM_TIME_KEY + DIM_COURSE_KEY + DIM_GEN_KEY
(전) USER_CNT  : EC_USER_CNT_PK  : DIM_COURSE_KEY + DIM_TIME_KEY+ DIM_GEN_KEY


제대로된 인덱스 생성 후 sql 성능 재 비교

hint 사용

SELECT /*+ ORDERED USE_NL(A B) */
       A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       SUM(B.USER_CNT)
  FROM EC_COURSE_CD A, EC_USER_CNT B
 WHERE A.DIM_COURSE_KEY =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
   AND B.DIM_TIME_KEY LIKE '2001%'
 GROUP BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.01       0.00         18        241          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.02       0.01         18        242          0         109

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      TABLE ACCESS (FULL) OF 'EC_COURSE_CD'
    952      INDEX (RANGE SCAN) OF 'EC_USER_CNT_IDX' (NON-UNIQUE)

Suppressing 사용

SELECT A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       SUM(B.USER_CNT)
  FROM EC_COURSE_CD A, EC_USER_CNT B
 WHERE A.DIM_COURSE_KEY + 0 =  B.DIM_COURSE_KEY
   AND A.DIM_BIG_KEY    = '02'
   AND B.DIM_TIME_KEY LIKE '2001%'
 GROUP BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.00       0.00          0        241          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.00       0.00          0        241          0         109

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      TABLE ACCESS (FULL) OF 'EC_COURSE_CD'
    952      INDEX (RANGE SCAN) OF 'EC_USER_CNT_IDX' (NON-UNIQUE)


view 사용

SELECT A.DIM_COURSE_NAME,B.DIM_TIME_KEY,
       SUM(B.USER_CNT)
  FROM (SELECT DIM_COURSE_KEY,
               DIM_COURSE_NAME,
               ROWNUM
          FROM EC_COURSE_CD
         WHERE DIM_BIG_KEY = '02') A,
        EC_USER_CNT B
 WHERE A.DIM_COURSE_KEY =  B.DIM_COURSE_KEY
   AND B.DIM_TIME_KEY LIKE '2001%'
 GROUP BY A.DIM_COURSE_NAME, B.DIM_TIME_KEY
call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        9      0.01       0.00          0        241          0         109
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       11      0.01       0.00          0        241          0         109

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
    109   SORT (GROUP BY)
    952    TABLE ACCESS (BY INDEX ROWID) OF 'EC_USER_CNT'
   1004     NESTED LOOPS
     51      VIEW
     51       COUNT
     51        TABLE ACCESS (FULL) OF 'EC_COURSE_CD'
    952      INDEX (RANGE SCAN) OF 'EC_USER_CNT_IDX' (NON-UNIQUE)