프린트 하기

OS 환경 : Oracle Linux 8.4 (64bit)

 

DB 환경 : Oracle Database 19.27.0.0

 

방법 : 오라클 19c 통계정보 대량 이관시 병렬 처리

지난 게시글에서 통계정보를 이관하는 방법을 설명했음
참고 : 오라클 19c 통계정보 이관 방법 ( https://positivemh.tistory.com/1265 )
규모가 작은 db의 경우 이 방식으로만 이관해도 큰 무리가 없음
하지만 db 크기가 큰 경우 이 방식으로만 이관하게되면 속도가 너무 느려 프로시저가 끝나질 않음

(20250820 업데이트 : 버그로 인한 속도 저하였음, 일반적으로라면 db 크기가 커도 빠르게 끝나야 정상임)
그리고 dbms_stats.import_database_stats에는 병렬 옵션이 존재하지 않음
본문에서는 이런 경우 수동으로 세션을 여러개로 나눠서 통게 import 시 테이블 레벨로 이관하는 방법을 설명함
그리고 import_database_stats과 import_table_stats 두개의 시간도 같이 측정해봄

 

 

테스트
샘플 유저 및 테이블, 인덱스 등 생성
참고 : 오라클 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_STATS');
 
PL/SQL procedure successfully completed.

 

 

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

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

 

 

all_table_stats 테이블 count 확인

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

약 150만건이 저장됨

 

 

참고1. 통계정보 export 시 statown과 stattab만 넣었기 때문에 statid는 모두 NULL로 되어있음

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

 

 

참고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_STATS'
    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_STATS                ALL_TABLE_STATS      NORMAL     STATID, TYPE, C5, C1, C2, C3, C4, VERSION          VALID

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

 

 

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

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

 

 

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 - 16:07:05 up 30 min,  2 users,  load average: 1.01, 0.72, 0.68
Tasks: 378 total,   2 running, 376 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  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.0 us,  0.0 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.3 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  : 99.3 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.3 hi,  0.3 si,  0.0 st  <<<<<<<<
%Cpu5  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu6  :  0.7 us,  0.7 sy,  0.0 ni, 97.0 id,  0.0 wa,  1.7 hi,  0.0 si,  0.0 st
%Cpu7  :  0.0 us,  0.0 sy,  0.0 ni, 96.0 id,  4.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu8  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu9  :  0.0 us,  0.0 sy,  0.0 ni, 99.3 id,  0.3 wa,  0.3 hi,  0.0 si,  0.0 st
MiB Mem :   7932.5 total,     52.2 free,   1343.4 used,   6536.9 buff/cache
MiB Swap:  10240.0 total,  10227.9 free,     12.1 used.   4166.1 avail Mem
 
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   2331 oracle    20   0 2984868   2.1g   2.0g R 100.0  26.6  17:39.92 oracle_2331_ora
   1692 oracle    -2   0 2935420  64480  61408 S   1.0   0.8   0:16.42 ora_vktm_ora19d
   2885 oracle    20   0  264360   4636   3716 R   0.7   0.1   0:06.01 top
    251 root       0 -20       0      0      0 I   0.3   0.0   0:00.13 kworker/7:1H-kblockd
    955 root      20   0  499412  11548   9736 S   0.3   0.1   0:01.35 vmtoolsd
   1729 oracle    20   0 2936712  98472  94492 S   0.3   1.2   0:00.55 ora_dbrm_ora19d

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 - 16:07:13 Primary  r/w ORA1 up:  31t,   1 sn,  1 ins,  0 er,  23G sz, 2.3G sga,    0%fra, archivelog            9.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  10.4    1  1.0    1    0    0    0  162k   1M 3.7m  0.4 21.5  4.2 17.3 100k    0  14.4 598M     0    0  0.6 443u  9.8  0.2
 
EVENT (RT)                                                        SESSIONS  WAIT  TIME   AVG  %DBT                      WAIT_CLASS
log file parallel write                                                      956  1.5s  1.5m  41.8                      System I/O
DB CPU                                                                            970m        27.9
db file sequential read                                                       82  940m   12m  27.2                        User I/O
latch free                                                                    68   30m  518u     1                           Other
oracle thread bootstrap                                                        1   30m   31m   0.9                           Other
 
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   197     2331 SYS      sqlplus@o DED SYS$USE INS c13dgxma46u7s 3.6t  103  100  22M ACT RUN            CPU Runqueue        7.2m

빠르게 나타났다 사라지는 insert 쿼리들에 %CPU가 103으로 높은 값이 찍힘
oratop에 걸리는 쿼리는 대부분 insert가 많음(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
Enter sql_id: c13dgxma46u7s
 
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  c13dgxma46u7s, 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_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.su
 
Plan hash value: 3696387946
 
------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name                      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                           |                           |       |       |   948 (100)|          |
|   1 |  LOAD TABLE CONVENTIONAL                   | DBMS_STATS_ID_MAP_TAB     |       |       |            |          |
|   2 |   HASH UNIQUE                              |                           |     1 |   436 |   948   (1)| 00:00:01 |
|   3 |    NESTED LOOPS                            |                           |     1 |   436 |   947   (0)| 00:00:01 |
|   4 |     NESTED LOOPS                           |                           |     1 |   436 |   947   (0)| 00:00:01 |
|   5 |      NESTED LOOPS                          |                           |     1 |   424 |   945   (0)| 00:00:01 |
|   6 |       NESTED LOOPS                         |                           |     1 |   396 |   943   (0)| 00:00:01 |
|   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 |
|  10 |         BUFFER SORT                        |                           |     1 |    15 |     1   (0)| 00:00:01 |
|  11 |          TABLE ACCESS BY INDEX ROWID       | USER$                     |     1 |    15 |     1   (0)| 00:00:01 |
|* 12 |           INDEX UNIQUE SCAN                | I_USER1                   |     1 |       |     0   (0)|          |
|* 13 |        INDEX RANGE SCAN                    | I_OBJ2                    |     1 |    35 |     1   (0)| 00:00:01 |
|* 14 |       VIEW PUSHED PREDICATE                | TABSUBPARTV$              |     1 |    28 |     2   (0)| 00:00:01 |
|  15 |        WINDOW BUFFER                       |                           |    32 |   480 |     2   (0)| 00:00:01 |
|* 16 |         TABLE ACCESS BY INDEX ROWID        | TABSUBPART$               |    32 |   480 |     2   (0)| 00:00:01 |
|* 17 |          INDEX RANGE SCAN                  | I_TABSUBPART_POBJSUBPART$ |    32 |       |     1   (0)| 00:00:01 |
|* 18 |      INDEX RANGE SCAN                      | I_OBJ1                    |     1 |       |     1   (0)| 00:00:01 |
|* 19 |     TABLE ACCESS BY INDEX ROWID            | OBJ$                      |     1 |    12 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------------------------
 
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")))
  12 - access("U"."NAME"=:1)
  13 - 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))
  14 - filter("S"."N13"="TSP"."SUBPART#")
  16 - filter(BITAND("FLAGS",8388608)=0)
  17 - access("POBJ#"="OP"."OBJ#")
  18 - access("TSP"."OBJ#"="OS"."OBJ#" AND "OS"."TYPE#"=34)
       filter("OS"."TYPE#"=34)
  19 - 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_STATS 테이블은 인덱스 range 스캔함
하지만 id 9를 보면 statid is null로 먼저 인덱스를 엑세스하고 있음
그부분에서 예상 cost도 높음, 이부분에 대해선 다음 게시글에서 테스트함, 본문에선 세션을 여러개로 나눠서 병렬 처리효과를 보는 테스트만 작성함

다음글 참고 : 오라클 19c 통계정보 대량 이관 분석 및 속도 개선 ( https://positivemh.tistory.com/1268 )

 

 

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
---------- ---------- ------------------------------ ---------- ----------
       197      55845 Import Database Statistics           4189      11851

1시간 30분이 지났음에도 아직 진행중임

 

 

dbms_stats.import_database_stats에는 병렬(parallel) 옵션이 없음
그렇기 때문에 통계정보 import 시 위와 같은 한 세션에서 테이블별 개별 insert문을 실행하여 import 함
테이블이 10000개인데도 이렇게 느리다면 실제 운영 환경에서는 훨씬 더 느리게 동작할것임

 

 

이렇게 통계정보를 넣게되면 너무 느리기 때문에 아래와 같이 유저별로 나눠서 import를 하는게 좋음
테이블 단위 import 구문 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SQL> 
set lines 200 pages 50000
spool statimport_all.sql
select 'exec dbms_stats.import_table_stats(ownname=>'''||owner||''', tabname=>'''||table_name||''', cascade=>true, statown=>''SYSTEM'', stattab=>''ALL_TABLE_STATS'');' cmd
from dba_tables
where owner in (select username from dba_users where oracle_maintained = 'N');
spool off
 
CMD
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
exec dbms_stats.import_table_stats(ownname=>'IMSI001', tabname=>'IMSI001_TBL001', cascade=>true, statown=>'SYSTEM', stattab=>'ALL_TABLE_STATS');
exec dbms_stats.import_table_stats(ownname=>'IMSI001', tabname=>'IMSI001_TBL002', cascade=>true, statown=>'SYSTEM', stattab=>'ALL_TABLE_STATS');
..
exec dbms_stats.import_table_stats(ownname=>'IMSI100', tabname=>'IMSI100_TBL099', cascade=>true, statown=>'SYSTEM', stattab=>'ALL_TABLE_STATS');
exec dbms_stats.import_table_stats(ownname=>'IMSI100', tabname=>'IMSI100_TBL100', cascade=>true, statown=>'SYSTEM', stattab=>'ALL_TABLE_STATS');
 
10000 rows selected.

 

 

statimport_all.sql 파일에서 명령어를 제외한 구문 삭제

1
2
$ vi statimport_all.sql
sql 구문 및 cmd, ----, 10000 rows selected. 등 텍스트 제거

 

 

statimport_all.sql 파일을 원하는 세션 갯수만큼 split 명령으로 쪼개기
나의 경우 1000줄씩 나눠서 10개 파일을 만듦

1
2
3
4
5
$ mkdir -p statimport_job
$ mv statimport_all.sql statimport_job
$ cd statimport_job
$ split -l 1000 statimport_all.sql statimport_part_
$ for f in statimport_part_*;  do mv "$f" "$f.sql" ; done

 

 

파일 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ls -al
total 3972
drwxr-xr-x   2 oracle oinstall    4096 Aug 10 17:36 .
drwx------. 10 oracle oinstall    4096 Aug 10 17:34 ..
-rw-r--r--   1 oracle oinstall 2010000 Aug 10 17:30 statimport_all.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_aa.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_ab.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_ac.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_ad.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_ae.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_af.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_ag.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_ah.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_ai.sql
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:35 statimport_part_aj.sql

정상적으로 파일이 10개로 나누어짐

 

 

해당 파일들을 sqlplus를 각각 열어서 실행할수도 있고

1
2
3
4
5
6
7
8
9
10
#세션1
$ sqlplus / as sysdba
SQL> @statimport_part_aa.sql
 
#세션2
$ sqlplus / as sysdba
SQL> @statimport_part_ab.sql
 
#세션n
...

 

 

nohup를 이용해 한번에 10개 세션을 열어서 수행할수도 있음
run_all.sh 쉘파일 생성

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
$ vi run_all.sh
#!/bin/bash
main_log="statimport_all.log"
 
# 로그 초기화
> "$main_log"
 
start_ts=$(date +%s)
echo "===== START: $(date -Is) =====" | tee -a "$main_log"
 
# 각 파일 실행 (백그라운드)
for f in statimport_part_*.sql; do
    log_file="${f%.sql}.log"
    echo "[START] $f at $(date -Is)" >> "$main_log"
    sqlplus -s / as sysdba @"$f" > "$log_file" 2>&1 &
done
 
# 모든 백그라운드 종료 대기
wait
 
end_ts=$(date +%s)
elapsed=$((end_ts - start_ts))
 
echo "===== END  : $(date -Is) ====="   | tee -a "$main_log"
printf "TOTAL ELAPSED: %02d:%02d:%02d\n" \
       $((elapsed/3600)) $(((elapsed%3600)/60)) $((elapsed%60)) | tee -a "$main_log"

 

 

권한 부여

1
$ chmod +x run_all.sh

 

 

실행

1
2
3
$ nohup ./run_all.sh &
$ jobs
[1]+  Running                 nohup ./run_all.sh &

nohup으로 수행됨

 

 

top 확인

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
$ top
top - 18:11:56 up 1 min,  3 users,  load average: 2.68, 0.85, 0.30
Tasks: 424 total,  12 running, 412 sleeping,   0 stopped,   0 zombie
%Cpu0  : 94.1 us,  0.7 sy,  0.0 ni,  1.0 id,  3.6 wa,  0.3 hi,  0.3 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu1  : 98.7 us,  0.0 sy,  0.0 ni,  1.0 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu2  : 98.0 us,  0.3 sy,  0.0 ni,  1.0 id,  0.3 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu3  : 96.3 us,  0.3 sy,  0.0 ni,  0.3 id,  2.7 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu4  : 93.7 us,  0.3 sy,  0.0 ni,  1.0 id,  4.7 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu5  : 96.0 us,  0.0 sy,  0.0 ni,  1.7 id,  2.0 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu6  : 97.3 us,  0.0 sy,  0.0 ni,  1.7 id,  0.7 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu7  : 98.3 us,  0.7 sy,  0.0 ni,  0.7 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu8  : 93.0 us,  0.7 sy,  0.0 ni,  4.0 id,  2.0 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
%Cpu9  : 94.3 us,  0.3 sy,  0.0 ni,  0.3 id,  4.7 wa,  0.3 hi,  0.0 si,  0.0 st  <<<<<<<<<<<<<<<<<
MiB Mem :   7932.5 total,   1929.0 free,   1244.4 used,   4759.1 buff/cache
MiB Swap:  10240.0 total,  10240.0 free,      0.0 used.   4191.8 avail Mem
 
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   2908 oracle    20   0 2943736 296356 289024 R  99.0   3.6   0:11.02 oracle_2908_ora
   2907 oracle    20   0 2941684 288564 281196 R  98.3   3.6   0:11.02 oracle_2907_ora
   2906 oracle    20   0 2947852 298464 291020 R  97.7   3.7   0:10.93 oracle_2906_ora
   2899 oracle    20   0 2943796 320032 311416 R  96.3   3.9   0:10.81 oracle_2899_ora
   2903 oracle    20   0 2956048 318568 311140 R  96.3   3.9   0:10.86 oracle_2903_ora
   2900 oracle    20   0 2943756 313064 305264 R  96.0   3.9   0:10.64 oracle_2900_ora
   2893 oracle    20   0 2943736 306880 299372 R  94.4   3.8   0:10.44 oracle_2893_ora
   2896 oracle    20   0 2951944 317084 309688 R  94.4   3.9   0:10.98 oracle_2896_ora
   2905 oracle    20   0 2943736 300496 293260 R  94.0   3.7   0:10.89 oracle_2905_ora
   2909 oracle    20   0 2941692 300496 293040 R  93.7   3.7   0:10.75 oracle_2909_ora
   1760 oracle    20   0 2935172  71876  69016 S   0.7   0.9   0:00.13 ora_lg00_ora19d
   1711 oracle    -2   0 2935420  64288  61220 S   0.3   0.8   0:00.63 ora_vktm_ora19d
   1740 oracle    20   0 2965376  67748  64520 S   0.3   0.8   0:00.11 ora_vkrm_ora19d

cpu를 모두 사용함

 

 

oratop 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/app/oracle/product/19c/suptools/oratop/oratop / as sysdba -rf
Oracle 19c - 18:11:54 Primary  r/w ORA1 up: 1.4t,  13 sn,  1 ins,  0 er,  18G sz, 2.3G sga,    0%fra, archivelog           25.2%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  21.7    2  2.5    9    0    3    0    1M  10M 553u 10.6  439  411 27.5 707k    0  41.6 391M  1.9k  8.9 17.7 1.4m 15.6  9.6
 
EVENT (RT)                                                        SESSIONS  WAIT  TIME   AVG  %DBT                      WAIT_CLASS
read by other session                                                        98k   22s  224u    25                        User I/O
cursor: pin S wait on X                                                      232   20s   87m  23.0                     Concurrency
db file sequential read                                                      23k   13s  580u    15                        User I/O
external table read                                                            1  9.8s  9.8s  11.1                        User I/O
log file parallel write                                                  1  1.3k  7.4s  5.9m   8.5                      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   105     2908 SYS      sqlplus@o DED SYS$USE INS 7habw4pk381bv 175u   67 10.4   4M ACT RUN            On CPU              175u
 1   249     2896 SYS      sqlplus@o DED SYS$USE INS 7habw4pk381bv  16u 65.1  9.7   4M ACT RUN            On CPU               16u
 1   441     2906 SYS      sqlplus@o DED SYS$USE INS 7habw4pk381bv 181u 64.7   10 3.9M ACT RUN            On CPU              181u
 1    55     2907 SYS      sqlplus@o DED SYS$USE UPS 3dbzmtf9ahvzt 1.0s 64.2 10.1   4M ACT WAI Concurrenc buffer busy waits    11m
 1   392     2905 SYS      sqlplus@o DED SYS$USE SEL 8rhyb2w8nrzjr 1.0s 62.9 10.2 4.2M ACT RUN            On CPU               14u
 1   201     2899 SYS      sqlplus@o DED SYS$USE INS 3v214syvr26ac 1.0s 62.8  9.8 4.6M ACT RUN            On CPU              168u
 1   296     2903 SYS      sqlplus@o DED SYS$USE INS 3v214syvr26ac 1.0s 62.2 10.0 4.4M ACT RUN            On CPU               51u
 1   151     2909 SYS      sqlplus@o DED SYS$USE UPS 3dbzmtf9ahvzt 1.0s 62.1 10.2 4.1M ACT WAI Concurrenc buffer busy waits    12m
 1     7     2893 SYS      sqlplus@o DED SYS$USE INS 7habw4pk381bv  40u 61.4  9.7   4M ACT RUN            On CPU               40u
 1   343     2900 SYS      sqlplus@o DED SYS$USE INS du1vfy7c7zvf2 1.0s 60.7 10.0   6M ACT I/O User I/O   db file sequential   11m

oratop에도 동시에 많은 세션들이 실행중인게 보임

 

 

로그 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ tail -300f statimport_all.log
===== START: 2025-08-10T18:11:39+09:00 =====
[START] statimport_part_aa.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_ab.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_ac.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_ad.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_ae.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_af.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_ag.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_ah.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_ai.sql at 2025-08-10T18:11:39+09:00
[START] statimport_part_aj.sql at 2025-08-10T18:11:39+09:00
 
===== END  : 2025-08-10T18:23:59+09:00 =====
TOTAL ELAPSED: 00:12:20

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

 

 

실제로 다 수행되었는지 확인

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
$ ls -al
total 4424
drwxr-xr-x   2 oracle oinstall    4096 Aug 10 19:05 .
drwx------. 10 oracle oinstall    4096 Aug 10 19:05 ..
-rw-------   1 oracle oinstall     114 Aug 10 18:23 nohup.out
-rwxr-xr-x   1 oracle oinstall     657 Aug 10 18:03 run_all.sh
-rw-r--r--   1 oracle oinstall     714 Aug 10 18:23 statimport_all.log
-rw-r--r--   1 oracle oinstall 2010000 Aug 10 17:51 statimport_all.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_aa.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_aa.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_ab.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_ab.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_ac.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_ac.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_ad.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_ad.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_ae.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_ae.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_af.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_af.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_ag.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_ag.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_ah.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_ah.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_ai.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_ai.sql
-rw-r--r--   1 oracle oinstall   43000 Aug 10 18:23 statimport_part_aj.log
-rw-r--r--   1 oracle oinstall  201000 Aug 10 17:52 statimport_part_aj.sql
 
$ grep "PL/SQL procedure successfully completed." *.log | wc -l
10000

통계정보 import 시 "PL/SQL procedure successfully completed." 구문이 출력되는데 이 구문이 10000개로 정확함

 

 

결론 :
대용량 db에서 통계정보 전체를 이관할(import) 경우 일반적인 방법으로 하면 느릴수 있기때문에
테이블 단위나 스키마 단위로 쪼개서 동시에 여러세션에서 import 하면 훨씬 빠르게 작업을 마칠 수 있음

 

 

다음 게시글에선 본문처럼 세션을 쪼개지않고 statid만 추가해줌으로써 대량 통계이관을 빠르게 수행한 내용을 설명함

오라클 19c 통계정보 대량 이관 분석 및 속도 개선 ( https://positivemh.tistory.com/1268 )

 

 

참조 : 

오라클 19c 유저 100개, 테이블 100개, 인덱스 100개, 데이터 1000건 생성 ( https://positivemh.tistory.com/1266 )
오라클 19c 통계정보 이관 방법 ( https://positivemh.tistory.com/1265 )

오라클 19c 통계정보 대량 이관 분석 및 속도 개선 ( https://positivemh.tistory.com/1268 )
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/