프린트 하기

내맘대로긍정이 알려주는

Oracle 23ai 신기능
무료 세미나 발표자료

다운로드 trending_flat
2024
10.12
14:00

OS환경 : Oracle Linux 7.6 (64bit)

 

DB 환경 : Oracle Database 19.11.0.0

 

방법 : sqlp 실기튜닝실습 20-1 인덱스 최적화 구성 devDo 

devDo님의 블로그를 참고하여 테이블 생성 후 실습함

sqlp 실기 준비 스터디를 진행하면서 나온 스터디원의 답안과 모법답안을 작성함

* 문제 쿼리는 문제와 동일한 실행계획을 표현하기위해 힌트를 사용해서 고정함

* 쿼리 실행 유저는 IMSI 유저를 사용함(다른 유저명으로 생성해서 사용해도 문제없음)

* 본문 샘플데이터의 분포, DB 버전이 문제와 동일하지 않기 때문에 문제와 답안의 성능(결과)가 devDo님 결과와 다를수있음

 

 

문제) 위의 쿼리에 대한 trace를 보고 튜닝을 하시오.

인덱스 구성

1
2
3
pk_idx_manuf20 : m_code
pk_idx_product20 : p_code + prod_id
idx_order20_01 : orderdt + order_qty + prod_id

 

 

SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SQL>
select /*+ gather_plan_statistics leading(m p o) use_nl(p) use_nl(o) index(p pk_idx_product20) index(m pk_idx_manuf20) index(o idx_order20_01) */
p.prod_nm, p.prod_id, o.order_qty
from manuf20 m, product20 p, order20 o
where m.m_code like 'A%'
and m.m_code = p.p_code
and p.prod_id = o.prod_id
and o.orderdt = '20001114'
and o.order_qty > '9000';
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULLNULL'ADVANCED ALLSTATS LAST'));
----------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name     | Starts | E-Rows|E-Bytes| Cost (%CPU)| E-Time | A-Rows |    A-Time  | Buffers |
----------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                  |     1 |       |       | 10662 (100)|        |      4 |00:00:00.01 |    2065 |
|   1 |  NESTED LOOPS                      |                  |     1 |   397 | 15086 | 10662   (1)| 00:00:01 |      4 |00:00:00.01 |    2065 |
|   2 |   NESTED LOOPS                       |              |     1 |   900 | 16200 |  7957   (1)| 00:00:01 |   1000 |00:00:00.01 |    36 |
|*  3 |    INDEX RANGE SCAN                   | PK_IDX_MANUF20   |    1 |  7913 | 47478 |    20   (0)| 00:00:01 |    100 |00:00:00.01 |      4 |
|   4 |    TABLE ACCESS BY INDEX ROWID BATCHED| PRODUCT20       |    100 |     1 |    12 |     2   (0)| 00:00:01 |   1000 |00:00:00.01 |     32 |
|*  5 |     INDEX RANGE SCAN                  | PK_IDX_PRODUCT20 |    100 |    90 |       |     1   (0)| 00:00:01 |   1000 |00:00:00.01 |    25 |
|*  6 |   INDEX RANGE SCAN                  | IDX_ORDER20_01   |   1000 |     1 |    20 |     3   (0)| 00:00:01 |      4 |00:00:00.02 |    2029 |
----------------------------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access("M"."M_CODE" LIKE 'A%')
       filter("M"."M_CODE" LIKE 'A%')
   5 - access("M"."M_CODE"="P"."P_CODE")
       filter("P"."P_CODE" LIKE 'A%')
   6 - access("O"."ORDERDT"='20001114' AND "O"."ORDER_QTY">'9000' AND "P"."PROD_ID"="O"."PROD_ID" AND "O"."ORDER_QTY" IS NOT NULL)
       filter("P"."PROD_ID"="O"."PROD_ID")

 

 

샘플 데이터 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
-------샘플데이터 생성 시작-------
-- 테이블 생성
drop table manuf20 purge;
drop table product20 purge;
drop table order20 purge;
create table manuf20 (m_code varchar2(6)) nologging;
create table product20 (prod_nm varchar2(4), prod_id number, p_code varchar2(6)) nologging;
create table order20 (orderdt varchar2(8), prod_id number, order_qty varchar2(8)) nologging;
 
-- 데이터 생성
DECLARE
TYPE tbl_ins IS TABLE OF manuf20%ROWTYPE INDEX BY BINARY_INTEGER;
w_ins tbl_ins;
BEGIN
FOR i IN 1..10000 LOOP 
   w_ins(i).m_code := 'B'||i;
END LOOP;
FORALL i in 1..10000 INSERT INTO manuf20 VALUES w_ins(i);
   COMMIT;
END;
/
 
DECLARE
TYPE tbl_ins IS TABLE OF manuf20%ROWTYPE INDEX BY BINARY_INTEGER;
w_ins tbl_ins;
BEGIN
FOR i IN 1..100 LOOP 
   w_ins(i).m_code := 'A'||i;
END LOOP;
FORALL i in 1..100 INSERT INTO manuf20 VALUES w_ins(i);
   COMMIT;
END;
/
 
DECLARE
TYPE tbl_ins IS TABLE OF product20%ROWTYPE INDEX BY BINARY_INTEGER;
w_ins tbl_ins;
BEGIN
FOR i IN 1..1000 LOOP 
   w_ins(i).prod_nm := dbms_random.string('x',4);
   w_ins(i).prod_id := i;
   w_ins(i).p_code := 'A'||ceil(i/100);
END LOOP;
FORALL i in 1..1000 INSERT INTO product20 VALUES w_ins(i);
   COMMIT;
END;
/
 
DECLARE
TYPE tbl_ins IS TABLE OF order20%ROWTYPE INDEX BY BINARY_INTEGER;
w_ins tbl_ins;
BEGIN
FOR i IN 1..100000 LOOP 
   w_ins(i).orderdt := '200011'||to_char( round(dbms_random.value(1,28)), 'FM09');
   w_ins(i).prod_id := i;
   w_ins(i).order_qty := ceil(dbms_random.value(1100000));
END LOOP;
FORALL i in 1..100000 INSERT INTO order20 VALUES w_ins(i);
   COMMIT;
END;
/
 
alter table manuf20 logging;
alter table product20 logging;
alter table order20 logging;
 
drop index pk_idx_manuf20;
drop index pk_idx_product20;
drop index idx_order20_01;
create unique index pk_idx_manuf20 on manuf20(m_code);
create unique index pk_idx_product20 on product20(p_code, prod_id);
create index idx_order20_01 on order20(orderdt, order_qty, prod_id);
 
conn / as sysdba
select table_name, segment_created, last_analyzed from dba_tables where table_name in ('MANUF20','PRODUCT20','ORDER20');
exec dbms_stats.gather_table_stats('IMSI''MANUF20'cascade=>TRUE);
exec dbms_stats.gather_table_stats('IMSI''PRODUCT20'cascade=>TRUE);
exec dbms_stats.gather_table_stats('IMSI''ORDER20'cascade=>TRUE);
select table_name, segment_created, last_analyzed from dba_tables where table_name in ('MANUF20','PRODUCT20','ORDER20');
conn imsi/imsi

샘플 데이터 생성 후 통계정보를 수동으로 수집해줌

 

 

스터디원 답안

P님 답안

- 문제점 파악

product20 스캔 시 인덱스 스캔 후 모든 row를 찾았지만 table 엑세스를 해서 cr 블록을 많이읽음
order20 스캔 시 인덱스에서만 읽지만 cr 블록을 많이읽음
predicate 를 보면 QT로 인해 m.m_code like 'A%' 조건이 p.p_code like 'A%'로 복제됨

- 개선 방안
manuf20 테이블은 m_code 조건에서만 사용하고 select 절에는 없으므로 제거
대신 product20테이블에 p.p_code like 'A%' 조건 추가
product20 인덱스 추가 idx2_product : prod_id+p_code+prod_nm
테이블까지 안가고 인덱스에서만 모두 읽게끔 인덱스 생성

- 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
SQL>
drop index idx_product20_01 ;
create index idx_product20_01 on product20(prod_id, p_code, prod_nm);
select /*+ gather_plan_statistics leading(o p) use_nl(p) index(p idx_product20_01) index(o idx_order20_01) */
p.prod_nm, p.prod_id, o.order_qty
from product20 p, order20 o
where p.p_code like 'A%'
and p.prod_id = o.prod_id
and o.orderdt = '20001114'
and o.order_qty > '9000';
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULLNULL'ADVANCED ALLSTATS LAST'));
---------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation      | Name       | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers | Reads  |
---------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |      1 |         |         |     401 (100)|         |        4 |00:00:00.01 |      45 |        4 |
|   1 |  NESTED LOOPS      |           |      1 |     397 | 12704 |     401   (0)| 00:00:01 |        4 |00:00:00.01 |      45 |        4 |
|*  2 |   INDEX RANGE SCAN| IDX_ORDER20_01 |      1 |     397 |    7940 |       4   (0)| 00:00:01 |      385 |00:00:00.01 |       6 |        0 |
|*  3 |   INDEX RANGE SCAN| IDX_PRODUCT20_01   |    385 |       1 |      12 |       1   (0)| 00:00:01 |        4 |00:00:00.01 |      39 |        4 |
---------------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("O"."ORDERDT"='20001114' AND "O"."ORDER_QTY">'9000' AND "O"."ORDER_QTY" IS NOT NULL)
   3 - access("P"."PROD_ID"="O"."PROD_ID" AND "P"."P_CODE" LIKE 'A%')
       filter("P"."P_CODE" LIKE 'A%')

 

 

J님 답안

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SQL>
drop index idx_product20_01;
drop index idx_order20_01;
create index idx_product20_01 on product20 (p_code, prod_id, prod_nm);
create index idx_order20_01 on order20 (orderdt, prod_id, order_qty);
SELECT /*+ gather_plan_statistics
leading (o) use_hash (p) index (p idx_product20_01) index (o idx_order20_01) */
P.PROD_NM, P.PROD_ID, O.ORDER_QTY
FROM PRODUCT20 P, ORDER20 O
WHERE P.P_CODE LIKE 'A%'
AND P.PROD_ID = O.PROD_ID
AND O.ORDERDT = '20001114'
AND O.ORDER_QTY > '9000';
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULLNULL'ADVANCED ALLSTATS LAST'));
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation      | Name         | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers | Reads    |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             |        1 |        |       |    23 (100)|           |      4 |00:00:00.01 |        24 |     21 |    |    |      |
|*  1 |  HASH JOIN      |             |        1 |    397 | 12704 |    23     (0)| 00:00:01 |      4 |00:00:00.01 |        24 |     21 |  1797K|  1797K| 1559K (0)|
|*  2 |   INDEX RANGE SCAN| IDX_ORDER20_01   |        1 |    397 |  7940 |    18     (0)| 00:00:01 |    385 |00:00:00.01 |        18 |     17 |    |    |      |
|*  3 |   INDEX RANGE SCAN| IDX_PRODUCT20_01 |        1 |    900 | 10800 |     5     (0)| 00:00:01 |   1000 |00:00:00.01 |         6 |      4 |    |    |      |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("P"."PROD_ID"="O"."PROD_ID")
   2 - access("O"."ORDERDT"='20001114' AND "O"."ORDER_QTY">'9000')
       filter("O"."ORDER_QTY">'9000')
   3 - access("P"."P_CODE" LIKE 'A%')
       filter("P"."P_CODE" LIKE 'A%')

 

 

S님 답안

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
SQL>
drop index idx_product20_01;
drop index idx_order20_01;
drop index pk_idx_product20;
create index pk_idx_product20 on product20(p_code,prod_id,prod_nm);
create index idx_order20_01 on order20(prod_id,orderdt,order_qty);
select /*+ gather_plan_statistics leading(m p o) use_nl(p) use_nl(o) index(p pk_idx_product20) index(m pk_idx_manuf20) index(o idx_order20_01) */
p.prod_nm, p.prod_id, o.order_qty
from manuf20 m, product20 p, order20 o
where m.m_code like 'A%'
and m.m_code = p.p_code
and p.prod_id = o.prod_id
and o.orderdt = '20001114'
and o.order_qty > '9000';
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULLNULL'ADVANCED ALLSTATS LAST'));
------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation       | Name          | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time    | A-Rows |   A-Time   | Buffers | Reads  |
------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |              |      1 |    |    |  8850 (100)|        |      4 |00:00:00.01 |      88 |      9 |
|   1 |  NESTED LOOPS       |              |      1 |    397 | 15086 |  8850   (1)| 00:00:01 |      4 |00:00:00.01 |      88 |      9 |
|   2 |   NESTED LOOPS       |              |      1 |    900 | 16200 |  7950   (1)| 00:00:01 |   1000 |00:00:00.01 |      35 |      4 |
|*  3 |    INDEX RANGE SCAN| PK_IDX_MANUF20   |      1 |   7913 | 47478 |    20   (0)| 00:00:01 |    100 |00:00:00.01 |       3 |      0 |
|*  4 |    INDEX RANGE SCAN| PK_IDX_PRODUCT20 |    100 |      1 |    12 |     1   (0)| 00:00:01 |   1000 |00:00:00.01 |      32 |      4 |
|*  5 |   INDEX RANGE SCAN | IDX_ORDER20_01   |   1000 |      1 |    20 |     1   (0)| 00:00:01 |      4 |00:00:00.01 |      53 |      5 |
------------------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access("M"."M_CODE" LIKE 'A%')
       filter("M"."M_CODE" LIKE 'A%')
   4 - access("M"."M_CODE"="P"."P_CODE")
       filter("P"."P_CODE" LIKE 'A%')
   5 - access("P"."PROD_ID"="O"."PROD_ID" AND "O"."ORDERDT"='20001114' AND "O"."ORDER_QTY">'9000' AND "O"."ORDER_QTY" IS NOT NULL)

 

 

G님 답안

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
SQL>
drop index idx_product20_01;
drop index pk_idx_product20;
drop index idx_order20_01;
create unique index pk_idx_product20 on product20(p_code, prod_id);
create index idx_order20_01 on order20(prod_id, orderdt, order_qty);
select /*+ gather_plan_statistics leading(m p o) use_nl(p) use_nl(o) index(p pk_idx_product20) index(m pk_idx_manuf20) index(o idx_order20_01) */
p.prod_nm, p.prod_id, o.order_qty
from manuf20 m, product20 p, order20 o
where m.m_code like 'A%'
and m.m_code = p.p_code
and p.prod_id = o.prod_id
and o.orderdt = '20001114'
and o.order_qty > '9000';
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULLNULL'ADVANCED ALLSTATS LAST'));
-------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name         | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |    A-Time     | Buffers | Reads  |
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |          |    1 |       |       |  8858 (100)|       |      4 |00:00:00.01 |    87 |      8 |
|   1 |  NESTED LOOPS                  |          |    1 |    397 | 15086 |  8858   (1)| 00:00:01 |      4 |00:00:00.01 |    87 |      8 |
|   2 |   NESTED LOOPS                  |          |    1 |    900 | 16200 |  7957   (1)| 00:00:01 |   1000 |00:00:00.01 |    34 |      3 |
|*  3 |    INDEX RANGE SCAN              | PK_IDX_MANUF20     |    1 |   7913 | 47478 |    20   (0)| 00:00:01 |    100 |00:00:00.01 |     3 |      0 |
|   4 |    TABLE ACCESS BY INDEX ROWID BATCHED| PRODUCT20     |    100 |     1 |    12 |     2   (0)| 00:00:01 |   1000 |00:00:00.01 |    31 |      3 |
|*  5 |     INDEX RANGE SCAN              | PK_IDX_PRODUCT20 |    100 |    90 |       |     1   (0)| 00:00:01 |   1000 |00:00:00.01 |    25 |      3 |
|*  6 |   INDEX RANGE SCAN              | IDX_ORDER20_01     |   1000 |     1 |    20 |     1   (0)| 00:00:01 |      4 |00:00:00.01 |    53 |      5 |
-------------------------------------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access("M"."M_CODE" LIKE 'A%')
       filter("M"."M_CODE" LIKE 'A%')
   5 - access("M"."M_CODE"="P"."P_CODE")
       filter("P"."P_CODE" LIKE 'A%')
   6 - access("P"."PROD_ID"="O"."PROD_ID" AND "O"."ORDERDT"='20001114' AND "O"."ORDER_QTY">'9000' AND "O"."ORDER_QTY" IS NOT NULL)

 

 

모범답안

1. product20 인덱스 추가 idx2_product : p_code+prod_id+prod_nm
2. order20 인덱스 추가 idx2_order : prod_id+orderdt+order_qty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
SQL>
drop index idx_product20_01;
drop index idx_order20_01;
create index idx_order20_01 on order20(orderdt, order_qty, prod_id);
create index idx_product20_01 on product20(p_code, prod_id, prod_nm);
create index idx_order20_02 on order20(prod_id, orderdt, order_qty);
select /*+ gather_plan_statistics leading(m p o) use_nl(p) use_nl(o) index(p idx_product20_01) index(m pk_idx_manuf20) index(o idx_order20_02) */
p.prod_nm, p.prod_id, o.order_qty
from manuf20 m, product20 p, order20 o
where m.m_code like 'A%'
and m.m_code = p.p_code
and p.prod_id = o.prod_id
and o.orderdt = '20001114'
and o.order_qty > '9000';
 
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULLNULL'ADVANCED ALLSTATS LAST'));
------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation       | Name          | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time    | A-Rows |   A-Time   | Buffers | Reads  |
------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |              |      1 |    |    |  8850 (100)|        |      4 |00:00:00.01 |      88 |      9 |
|   1 |  NESTED LOOPS       |              |      1 |    397 | 15086 |  8850   (1)| 00:00:01 |      4 |00:00:00.01 |      88 |      9 |
|   2 |   NESTED LOOPS       |              |      1 |    900 | 16200 |  7950   (1)| 00:00:01 |   1000 |00:00:00.01 |      35 |      4 |
|*  3 |    INDEX RANGE SCAN| PK_IDX_MANUF20   |      1 |   7913 | 47478 |    20   (0)| 00:00:01 |    100 |00:00:00.01 |       3 |      0 |
|*  4 |    INDEX RANGE SCAN| IDX_PRODUCT20_01 |    100 |      1 |    12 |     1   (0)| 00:00:01 |   1000 |00:00:00.01 |      32 |      4 |
|*  5 |   INDEX RANGE SCAN | IDX_ORDER20_02   |   1000 |      1 |    20 |     1   (0)| 00:00:01 |      4 |00:00:00.01 |      53 |      5 |
------------------------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access("M"."M_CODE" LIKE 'A%')
       filter("M"."M_CODE" LIKE 'A%')
   4 - access("M"."M_CODE"="P"."P_CODE")
       filter("P"."P_CODE" LIKE 'A%')
   5 - access("P"."PROD_ID"="O"."PROD_ID" AND "O"."ORDERDT"='20001114' AND "O"."ORDER_QTY">'9000' AND "O"."ORDER_QTY" IS NOT NULL)

* 본문 샘플데이터의 분포가 문제와 동일하지 않기 때문에 문제와 답안의 성능(결과)가 devDo님 결과와 다를수있음

* 상세 설명은 devDo님 설명참조

 

 

참조 : 

https://blog.naver.com/oracledo/220666967413

https://blog.naver.com/oracledo/220671808253