프린트 하기

OS 환경 : Oracle Linux 8.4 (64bit)

 

DB 환경 : Oracle Database 19.27.0.0

 

방법 : 오라클 19c 통계정보 대량 이관 분석 및 속도 개선

지난 게시글에서 대용량 통계정보를 세션을 여러개로 나누어 병렬로 이관하는 방법을 설명했음
참고 : 오라클 19c 통계정보 대량 이관시 병렬 처리 ( https://positivemh.tistory.com/1267 )
그리고 지난 테스트에서 db 크기가 큰 경우 dbms_stats.import_database_stats 을 이용하면 속도가 너무 느려 프로시저가 끝나질 않는다고 얘기했었음

 

본문에서는 dbms_stats.export_database_stats, import_database_stats를 분석해보고
문제점을 개선해서 dbms_stats.import_database_stats도 빠르게 수행해봄
결론부터 말하자면 dbms_stats.export_database_stats 시 statid를 넣어줘야 빠르게 동작함
이유는 본문에서 설명함

 

 

테스트
샘플 유저 및 테이블, 인덱스 등 생성
참고 : 오라클 19c 유저 100개, 테이블 100개, 인덱스 100개, 데이터 1000건 생성 ( https://positivemh.tistory.com/1266 )
현재 db에는 유저 100개와 해당 유저에 테이블 100개, 인덱스 100개 생성 후 데이터도 1000건씩 생성되어있음
이 상태에서 통계정보를 export 한뒤 다시 import 하는 테스트를 진행함

 

 

통계정보 수집(db 레벨 수집)

1
2
3
4
5
6
7
8
9
10
11
12
13
SQL>
set timing on
BEGIN
  DBMS_STATS.GATHER_DATABASE_STATS(
    estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
    method_opt       => 'FOR ALL COLUMNS SIZE SKEWONLY',
    degree           => DBMS_STATS.AUTO_DEGREE,
    cascade          => TRUE
  );
END;
/
 
PL/SQL procedure successfully completed.

각 파라미터 의미
DBMS_STATS.GATHER_DATABASE_STATS : db 레벨에서 통계정보 수집
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE : 자동 샘플링, Oracle이 데이터 양과 통계 품질에 따라 적절한 샘플링 비율을 자동 결정
method_opt => 'FOR ALL COLUMNS SIZE SKEWONLY' : Oracle이 컬럼의 데이터 분포를 기반으로 히스토그램을 수집할 컬럼을 결정
degree => DBMS_STATS.AUTO_DEGREE : 병렬 자동 시스템 상황(코어 수, 부하 등)에 따라 Oracle이 병렬도를 자동 설정
cascade => TRUE 인덱스 포함 테이블뿐 아니라 해당 테이블에 속한 인덱스 통계도 함께 수집
참고 : 오라클 19c 통계정보 이관 방법 ( https://positivemh.tistory.com/1265 )

 

 

통계 저장용 테이블 생성

1
2
3
SQL> exec dbms_stats.create_stat_table(ownname => 'SYSTEM', stattab => 'ALL_TABLE_STATS2');
 
PL/SQL procedure successfully completed.

 

 

통계정보 export(export 시 statid도 ALL_TABLE_STATS_ID로 지정함)

(ALL_TABLE_STATS 테이블로 통계정보를 넣는작업)

1
2
3
SQL> exec dbms_stats.export_database_stats(statown => 'SYSTEM', stattab => 'ALL_TABLE_STATS2', statid => 'ALL_TABLE_STATS_ID');
 
PL/SQL procedure successfully completed.

 

 

all_table_stats 테이블 count 확인

1
2
3
4
5
SQL> select count(*) from system.all_table_stats2;
 
  COUNT(*)
----------
   1529704

약 150만건이 저장됨

 

 

참고1. 통계정보 export 시 statid도 넣었기 때문에 statid는 모두 ALL_TABLE_STATS_ID로 되어있음

1
2
3
4
5
6
7
8
SQL> 
select nvl(statid, 'NULL') statid, count(*) 
from system.all_table_stats2
group by statid;
   
STATID                COUNT(*)
------------------- ----------
ALL_TABLE_STATS_ID     1529704

 

 

참고2. stat 테이블에는 index도 존재함

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>
set lines 200 pages 1000
col owner for a15
col index_type for a10
col index_name for a20
col table_name for a30
col columns for a50
SELECT a.*, (SELECT status from dba_indexes where index_name = a.index_name and rownum<=1) status from (
SELECT
    ind.owner AS "OWNER",
    ind.table_name AS "TABLE_NAME",
    ind.index_name AS "INDEX_NAME",
    ind.index_type AS "INDEX_TYPE",
    LISTAGG(ind_col.column_name, ', ') WITHIN GROUP (ORDER BY ind_col.column_position) AS "COLUMNS"
FROM
    dba_indexes ind
JOIN
    dba_ind_columns ind_col
    ON ind.owner = ind_col.index_owner
    AND ind.index_name = ind_col.index_name
WHERE
    ind.table_name = 'ALL_TABLE_STATS2'
    AND ind.owner = 'SYSTEM'
GROUP BY
    ind.owner,
    ind.index_name,
    ind.index_type,
    ind.table_name
ORDER BY
    ind.owner,
    ind.index_name) a;
 
OWNER           TABLE_NAME                     INDEX_NAME           INDEX_TYPE COLUMNS                                            STATUS
--------------- ------------------------------ -------------------- ---------- -------------------------------------------------- --------
SYSTEM          ALL_TABLE_STATS2               ALL_TABLE_STATS2     NORMAL     STATID, TYPE, C5, C1, C2, C3, C4, VERSION          VALID

ALL_TABLE_STATS2에 인덱스 이름도 ALL_TABLE_STATS임 STATID, TYPE, C5, C1, C2, C3, C4, VERSION 컬럼으로 이루어져 있음

 

 

다시 이 통계정보를 db에 import(ALL_TABLE_STATS2 테이블에서 db 테이블로 통계정보를 넣는작업)

1
2
3
4
SQL> 
set timing on
exec dbms_stats.import_database_stats(statown => 'SYSTEM', stattab => 'ALL_TABLE_STATS2', statid => 'ALL_TABLE_STATS_ID');
(실행중)

 

 

v$session_longops 확인

1
2
3
4
5
6
7
8
9
10
SQL>
set lines 200 pages 1000
col opname for a30
select sid, serial#, opname, sofar, totalwork
from v$session_longops
where sofar != totalwork;
 
       SID    SERIAL# OPNAME                              SOFAR  TOTALWORK
---------- ---------- ------------------------------ ---------- ----------
       296      52026 Import Database Statistics           4303      11852

이전 테스트와는 다르게 5분도 안지났는데 sofat이 4300이 넘음

 

 

top 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ top
top - 20:09:42 up  1:59,  3 users,  load average: 1.24, 0.91, 0.50
Tasks: 396 total,   2 running, 394 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 96.5 us,  2.6 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.9 hi,  0.0 si,  0.0 st  <<<<<<<<<
%Cpu2  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu4  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu5  :  0.9 us,  0.9 sy,  0.0 ni, 98.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu6  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu7  :  0.0 us,  0.9 sy,  0.0 ni, 97.4 id,  0.0 wa,  1.7 hi,  0.0 si,  0.0 st
%Cpu8  :  3.5 us,  9.7 sy,  0.0 ni, 66.4 id, 17.7 wa,  0.9 hi,  1.8 si,  0.0 st
%Cpu9  :  0.0 us,  0.0 sy,  0.0 ni, 97.4 id,  0.0 wa,  2.6 hi,  0.0 si,  0.0 st
MiB Mem :   7932.5 total,     53.8 free,   1587.6 used,   6291.1 buff/cache
MiB Swap:  10240.0 total,  10219.9 free,     20.1 used.   4042.5 avail Mem
 
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   4112 oracle    20   0 2984876   1.6g   1.6g R  99.1  21.1   4:48.18 oracle_4112_ora
   1756 oracle    20   0 2966412  80556  76704 S   9.6   1.0   0:16.19 ora_lgwr_ora19d
    281 root       0 -20       0      0      0 I   4.3   0.0   0:02.31 kworker/8:1H-kblockd
   4165 oracle    20   0  264356   4628   3736 R   1.7   0.1   0:00.03 top
      1 root      20   0  238412  11016   8248 S   0.0   0.1   0:01.09 systemd
      2 root      20   0       0      0      0 S   0.0   0.0   0:00.01 kthreadd

여전히 cpu 10개중에 하나만 사용중임

 

 

oratop 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/app/oracle/product/19c/suptools/oratop/oratop / as sysdba -rf
Oracle 19c - 20:04:55 Primary  r/w ORA1 up: 1.9h,   2 sn,  1 ins,  0 er,  18G sz, 2.3G sga,    0%fra, archivelog            3.9%db
ID CPU  %CPU LOAD  AAS  ASC  ASI  ASW  ISW  REDO TEMP IORT MBPS IOPS  R/S  W/S  LIO GCPS  %FRE  PGA  NETV UTPS UCPS SQRT %DBC %DBW
 1  10   4.1  0.5  0.4    1    0    0    1  374k   1M 2.8m  1.4  123 15.3  108  62k    0  16.0 635M   589    0  8.4  77u  3.4  0.5
 
EVENT (RT)                                                        SESSIONS  WAIT  TIME   AVG  %DBT                      WAIT_CLASS
db file sequential read                                                      770  2.8s  3.6m  55.6                        User I/O
log file parallel write                                                     5.9k  1.5s  255u  30.5                      System I/O
DB CPU                                                                            340m         6.9
db file async I/O submit                                                     497  290m  585u   5.8                      System I/O
control file parallel write                                                   38   20m  547u   0.4                      System I/O
 
ID   SID     SPID USERNAME PROGRAM   SRV SERVICE OPN SQLID/BLOCKER  E/T %CPU %LIO  PGA STS STE WAIT_CLASS EVENT/OBJECT NAME    W/T
 1   296     4112 SYS      sqlplus@o DED SYS$USE INS 22x35t27br8tn 8.1t 75.9  100  48M ACT RUN            On CPU                7u

빠르게 나타났다 사라지는 insert 쿼리들에 %CPU가 75~90정도로 찍힘
oratop에 걸리는 쿼리는 대부분 select 가 많았고 insert는 가끔 관측됨(빠르게 수행되서 그런것으로 판단됨)

 

 

oratop 플랜확인
oratop에서 x 입력 후 sql_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
Enter sql_id: 22x35t27br8tn
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  22x35t27br8tn, child number 0
--------------------------------------
       insert /*+ OPT_PARAM('_parallel_syspls_obey_force' 'false')
*/         into sys.dbms_stats_id_map_tab (c5, c1, c2, c3, cn)
select /*+ leading(s) index(s) */          distinct s.c5, s.c1,
s.c2, s.c3, d.subpartition_name cn       from
"SYSTEM"."ALL_TABLE_STATS2" s,                    (select u.name
table_owner, op.name table_name,                       op.subname
partition_name, os.subname subpartition_name,
tsp.subpart# subpartition_position                    from user$
u, obj$ op, tabsubpartv$ tsp, obj$ os                    where
u.user# = op.owner# and op.type# = 19                       and
op.obj# = tsp.pobj#                       and tsp.obj# = os.obj#
and os.type# = 34) d       where s.c5 = :1 and s.c1 = :2
and s.type in ('T','C','E','P','H','B','t','c','M','U','G','L')
and s.n13 is not null          and s.c3 like 'SYS_%'         and
s.c5 = d.table_owner and s.c1 = d.table_name         and s.c2 =
d.partition_name         and s.n13 = d.s
 
Plan hash value: 4170016247
 
-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name                      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                            |                           |       |       |    19 (100)|          |
|   1 |  LOAD TABLE CONVENTIONAL                    | DBMS_STATS_ID_MAP_TAB     |       |       |            |          |
|   2 |   HASH UNIQUE                               |                           |     1 |   436 |    19   (6)| 00:00:01 |
|   3 |    NESTED LOOPS                             |                           |     1 |   436 |    18   (0)| 00:00:01 |
|   4 |     NESTED LOOPS                            |                           |     1 |   436 |    18   (0)| 00:00:01 |
|   5 |      NESTED LOOPS                           |                           |     1 |   424 |    16   (0)| 00:00:01 |
|   6 |       NESTED LOOPS                          |                           |     1 |   396 |    14   (0)| 00:00:01 |
|   7 |        MERGE JOIN CARTESIAN                 |                           |     1 |   361 |    13   (0)| 00:00:01 |
|   8 |         INLIST ITERATOR                     |                           |       |       |            |          |
|*  9 |          TABLE ACCESS BY INDEX ROWID BATCHED| ALL_TABLE_STATS2          |     1 |   346 |    12   (0)| 00:00:01 |
|* 10 |           INDEX RANGE SCAN                  | ALL_TABLE_STATS2          |     1 |       |    12   (0)| 00:00:01 |
|  11 |         BUFFER SORT                         |                           |     1 |    15 |     1   (0)| 00:00:01 |
|  12 |          TABLE ACCESS BY INDEX ROWID        | USER$                     |     1 |    15 |     1   (0)| 00:00:01 |
|* 13 |           INDEX UNIQUE SCAN                 | I_USER1                   |     1 |       |     0   (0)|          |
|* 14 |        INDEX RANGE SCAN                     | I_OBJ2                    |     1 |    35 |     1   (0)| 00:00:01 |
|* 15 |       VIEW PUSHED PREDICATE                 | TABSUBPARTV$              |     1 |    28 |     2   (0)| 00:00:01 |
|  16 |        WINDOW BUFFER                        |                           |    32 |   480 |     2   (0)| 00:00:01 |
|* 17 |         TABLE ACCESS BY INDEX ROWID         | TABSUBPART$               |    32 |   480 |     2   (0)| 00:00:01 |
|* 18 |          INDEX RANGE SCAN                   | I_TABSUBPART_POBJSUBPART$ |    32 |       |     1   (0)| 00:00:01 |
|* 19 |      INDEX RANGE SCAN                       | I_OBJ1                    |     1 |       |     1   (0)| 00:00:01 |
|* 20 |     TABLE ACCESS BY INDEX ROWID             | OBJ$                      |     1 |    12 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   9 - filter("S"."N13" IS NOT NULL)
  10 - access("S"."STATID"='ALL_TABLE_STATS_ID' AND (("S"."TYPE"='B' OR "S"."TYPE"='C' OR "S"."TYPE"='E' OR
              "S"."TYPE"='G' OR "S"."TYPE"='H' OR "S"."TYPE"='L' OR "S"."TYPE"='M' OR "S"."TYPE"='P' OR "S"."TYPE"='T' OR
              "S"."TYPE"='U' OR "S"."TYPE"='c' OR "S"."TYPE"='t')) AND "S"."C5"=:1 AND "S"."C1"=:2 AND "S"."C3" LIKE 'SYS_%')
       filter(("S"."C2" IS NOT NULL AND "S"."C3" LIKE 'SYS_%'))
  13 - access("U"."NAME"=:1)
  14 - access("U"."USER#"="OP"."OWNER#" AND "OP"."NAME"=:2 AND "S"."C2"="OP"."SUBNAME" AND "OP"."TYPE#"=19)
       filter(("OP"."SUBNAME" IS NOT NULL AND "S"."C2"="OP"."SUBNAME" AND "OP"."TYPE#"=19))
  15 - filter("S"."N13"="TSP"."SUBPART#")
  17 - filter(BITAND("FLAGS",8388608)=0)
  18 - access("POBJ#"="OP"."OBJ#")
  19 - access("TSP"."OBJ#"="OS"."OBJ#" AND "OS"."TYPE#"=34)
       filter("OS"."TYPE#"=34)
  20 - filter("S"."C3"<>"OS"."SUBNAME")
 
Note
-----
   - dynamic statistics used: dynamic sampling (level=2)
 
press Enter to return

DBMS_STATS_ID_MAP_TAB 테이블에 insert를 하는 쿼리임, ALL_TABLE_STATS2 테이블은 이전 테스트와 동일하게 인덱스 range 스캔함
하지만 자세히보면 8번에서 inlist 방식으로 동작하고, id 10에 보면 인덱스 첫번째 컬럼이 null이 아니라 엑세스에 모든 컬럼을 다 잘 이용함
그부분에서 예상 cost도 낮게잡힘

 

 

쿼리 비교(v$sql에서 sqlid로 sql_fulltext 확인)

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
#statid 미입력시 쿼리
SQL> 
insert /*+ OPT_PARAM('_parallel_syspls_obey_force' 'false') */ 
into sys.dbms_stats_id_map_tab (c5, c1, c2, c3, cn)
select /*+ leading(s) index(s) */
distinct s.c5, s.c1,s.c2, s.c3, d.subpartition_name cn
from "SYSTEM"."ALL_TABLE_STATS" s
, (select u.name table_owner, op.name table_name, op.subname partition_name, os.subname subpartition_name, tsp.subpart# subpartition_position
from user$ u, obj$ op, tabsubpartv$ tsp, obj$ os
where u.user# = op.owner# 
and op.type# = 19
and op.obj# = tsp.pobj#
and tsp.obj# = os.obj#
and os.type# = 34) d
where s.c5 = :1 
and s.c1 = :2
and s.type in ('T','C','E','P','H','B','t','c','M','U','G','L')
and s.n13 is not null
and s.c3 like 'SYS_%'
and s.c5 = d.table_owner 
and s.c1 = d.table_name
and s.c2 = d.partition_name
and s.n13 = d.subpartition_position
and s.c3 !=  d.subpartition_name and s.statid is null
 
#statid 입력시 쿼리
SQL> 
insert /*+ OPT_PARAM('_parallel_syspls_obey_force' 'false') */         
into sys.dbms_stats_id_map_tab (c5, c1, c2, c3, cn)
select /*+ leading(s) index(s) */ 
distinct s.c5, s.c1,s.c2, s.c3, d.subpartition_name cn
from "SYSTEM"."ALL_TABLE_STATS2" s
, (select u.name table_owner, op.name table_name, op.subname partition_name, os.subname subpartition_name, tsp.subpart# subpartition_position
from user$ u, obj$ op, tabsubpartv$ tsp, obj$ os
where u.user# = op.owner# 
and op.type# = 19 
and op.obj# = tsp.pobj# 
and tsp.obj# = os.obj#
and os.type# = 34) d
where s.c5 = :1 
and s.c1 = :2
and s.type in ('T','C','E','P','H','B','t','c','M','U','G','L')
and s.n13 is not null
and s.c3 like 'SYS_%'
and s.c5 = d.table_owner 
and s.c1 = d.table_name
and s.c2 = d.partition_name
and s.n13 = d.subpartition_position
and s.c3 !=  d.subpartition_name and s.statid = 'ALL_TABLE_STATS_ID'

statid 미입력시 쿼리에는 statid is null 조건이 들어가고, statid 입력시 쿼리에는 상수값 조건이 들어감

 

 

플랜 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#statid 미입력시 플랜
------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name                      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------------------------
..
|   7 |        MERGE JOIN CARTESIAN                |                           |     1 |   361 |   942   (0)| 00:00:01 |
|*  8 |         TABLE ACCESS BY INDEX ROWID BATCHED| ALL_TABLE_STATS           |     1 |   346 |   941   (0)| 00:00:01 |
|*  9 |          INDEX RANGE SCAN                  | ALL_TABLE_STATS           |     1 |       |   941   (0)| 00:00:01 |
 
#statid 입력시 플랜
------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name                      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------------------------
..
|   7 |        MERGE JOIN CARTESIAN                 |                           |     1 |   361 |    13   (0)| 00:00:01 |
|   8 |         INLIST ITERATOR                     |                           |       |       |            |          |
|*  9 |          TABLE ACCESS BY INDEX ROWID BATCHED| ALL_TABLE_STATS2          |     1 |   346 |    12   (0)| 00:00:01 |
|* 10 |           INDEX RANGE SCAN                  | ALL_TABLE_STATS2          |     1 |       |    12   (0)| 00:00:01 |

statid 입력시 플랜이 inlist 방식으로 동작하고 예상값이지만 cost도 낮음

 

 

Predicate Information 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#statid 미입력시 Predicate Information
Predicate Information (identified by operation id):
---------------------------------------------------
   8 - filter("S"."N13" IS NOT NULL)
   9 - access("S"."STATID" IS NULL AND "S"."C5"=:1 AND "S"."C1"=:2 AND "S"."C3" LIKE 'SYS_%')
       filter(("S"."C2" IS NOT NULL AND "S"."C1"=:2 AND "S"."C5"=:1 AND "S"."C3" LIKE 'SYS_%' AND
              INTERNAL_FUNCTION("S"."TYPE")))
 
#statid 입력시 Predicate Information
Predicate Information (identified by operation id):
---------------------------------------------------
   9 - filter("S"."N13" IS NOT NULL)
  10 - access("S"."STATID"='ALL_TABLE_STATS_ID' AND (("S"."TYPE"='B' OR "S"."TYPE"='C' OR "S"."TYPE"='E' OR
              "S"."TYPE"='G' OR "S"."TYPE"='H' OR "S"."TYPE"='L' OR "S"."TYPE"='M' OR "S"."TYPE"='P' OR "S"."TYPE"='T' OR
              "S"."TYPE"='U' OR "S"."TYPE"='c' OR "S"."TYPE"='t')) AND "S"."C5"=:1 AND "S"."C1"=:2 AND "S"."C3" LIKE 'SYS_%')
       filter(("S"."C2" IS NOT NULL AND "S"."C3" LIKE 'SYS_%'))

통계 테이블의 인덱스 컬럼이 STATID, TYPE, C5, C1, C2, C3, C4, VERSION 인데
statid 미입력시에는 두번째 컬럼인 type이 INTERNAL_FUNCTION으로 감싸져 있어 access에도 없고 filter에만 존재함
statid 입력시에는 STATID를 먼저 읽고 or로 여러개 작성된 type 컬럼을 inlist로 읽어 모두 비효율을 줄임

 

 

통계정보 import가 9분만에 끝남

1
2
3
4
5
SQL> 
set timing on
exec dbms_stats.import_database_stats(statown => 'SYSTEM', stattab => 'ALL_TABLE_STATS2', statid => 'ALL_TABLE_STATS_ID');
(실행완료)
Elapsed: 00:09:04.08

9분만에 완료됨

 

 

결론 :
이전글에서 대용량 db에서 통계정보 전체를 이관할(import) 경우 일반적인 방법으로 하면 느릴수 있다고 얘기했었음
하지만 이건 인덱스 선두 컬럼인 statid가 null이고, 오라클에서 내부적으로 통계 테이블을 읽을때 inlist가 동작하지 않아 느린것이었음
본문처럼 dbms_stats.export_database_stats에 statid를 넣어주고 import시에도 이 statid를 적어주면 내부적으로 inlist 방식으로 동작해 statid를 사용하지 않은 경우보다 훨씬 빠르게 수행됨
dbms_stats시 statid를 작성하지 않을 경우 기본값이 null이 들어가게됨, 이 부분만 추후 버전에서 datapump를 수행할때 자동으로 할당되는 job 이름처럼 리터럴값이 들어가게끔 패치가 된다면 매우 좋을듯함
만약 export 시 이미 statid를 넣지 않았을 경우 오라클 19c 통계정보 대량 이관시 병렬 처리 ( https://positivemh.tistory.com/1267 ) 게시글처럼 세션을 여러개 나눠서 수행하면 빠르게 import 가능함

 

 

참고로 statid 값이 null일때 수동으로 다른값을(IMSI) 넣었을때도 inlist 방식으로 빠르게 import 되었음(이 방식은 오라클이 만든 테이블을 변경하는 방식이기 때문에 비추천함)

1
2
3
4
5
6
7
8
9
10
11
12
SQL> update system.ALL_TABLE_STATS set statid='IMSI';
 
1529703 rows updated.
 
SQL> commit;
 
Commit complete.
 
SQL> 
set timing on
exec dbms_stats.import_database_stats(statown => 'SYSTEM', stattab => 'ALL_TABLE_STATS', statid => 'IMSI');
(빠름)

 

 

참조 : 

오라클 19c 통계정보 대량 이관시 병렬 처리 ( https://positivemh.tistory.com/1267 )
오라클 19c 유저 100개, 테이블 100개, 인덱스 100개, 데이터 1000건 생성 ( https://positivemh.tistory.com/1266 )
오라클 19c 통계정보 이관 방법 ( https://positivemh.tistory.com/1265 )
https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_STATS.html#GUID-0244C71F-40E4-4CC5-A1D3-08C96B8B9E16
https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_STATS.html#GUID-D3FF95A6-ECAE-41B9-90CB-95A68A86AE2D
https://dohdatabase.com/2023/12/18/how-to-export-and-import-statistics-faster-using-dbms_stats-in-parallel/
https://dohdatabase.com/2022/05/21/if-importing-statistics-using-dbms_stats-is-slow/