프린트 하기 URL 복사

OS 환경 : Oracle Linux 9.6 (64bit)

 

DB 환경 : Oracle Database 19.31.0.0

 

방법 : 오라클 19c Batch I/O 동작 및 버퍼 사용량 테스트

TABLE ACCESS BY INDEX ROWID BATCHED는 인덱스에서 일부 ROWID를 가져온 후 해당 ROWID가 가리키는 행을 블록 순서로 접근하도록 시도하는 기능임

 

CF가 좋은 경우에는 인덱스 키 순서와 테이블의 물리적인 저장 순서가 비슷하므로 일반적인 TABLE ACCESS BY INDEX ROWID 방식으로 접근해도 같은 블록의 행을 연속해서 읽을 가능성이 높음
따라서 Batch I/O 방식이 적용되더라도 성능 차이가 크지 않을 수 있음

 

반대로 CF가 나쁜 경우에는 인덱스 키 순서와 테이블의 물리적인 저장 순서가 다르기 때문에 테이블 블록을 반복해서 접근할 가능성이 높음
이때 일부 ROWID를 모아 블록 순서로 접근하면 같은 블록에 대한 반복 접근을 줄이고 I/O 효율을 높일 수 있음

 

이 기능이 적용되면 실행계획에 TABLE ACCESS BY INDEX ROWID BATCHED로 표시됨

 

그리고 Batch I/O 방식에서는 인덱스에서 읽은 ROWID 순서와 다르게 테이블 블록을 접근할 수 있기 때문에 인덱스 키 순서대로 행이 반환되지 않을 수 있음
오라클 공식 가이드로 ORDER BY가 없는 SQL은 원래 결과 순서를 보장하지 않는다고 명시하고 있기 때문에 정렬된 결과가 필요한 경우 ORDER BY를 명시해줘야 함

 

Batch I/O 방식을 제어하려면 세션 또는 시스템 레벨에서 "_optimizer_batch_table_access_by_rowid" 히든 파라미터를 사용하거나
SQL 레벨에서 BATCH_TABLE_ACCESS_BY_ROWID 또는 NO_BATCH_TABLE_ACCESS_BY_ROWID 힌트를 사용하면 됨

 

본문에서는 Batch I/O 방식 적용 여부에 따라 인덱스 키 순서가 유지되는지 확인하고 Buffers와 Reads 사용량에 어떤 차이가 발생하는지 확인해봄

 

 

테스트
사전 설정
배치 io 비활성화 후 별도 테이블에 삽입
배치 io 활성화 후 별도 테이블에 삽입
batch io 비활성화시 데이터 순서 확인
batch io 비활성화시 데이터 순서 확인

 

 

테스트
사전 설정

1
2
3
4
5
6
7
8
SQL>
set lines 250 pages 1000
set timing on
set serveroutput off
 
alter session set statistics_level = all;
alter session set optimizer_dynamic_sampling = 0;
alter session set "_optimizer_gather_stats_on_load" = false;

 

 

샘플 테이블 생성 및 데이터 삽입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SQL>
drop table t_batch_sort purge;
create table t_batch_sort (
    c1 number,
    c2 varchar2(1000)
);
 
insert into t_batch_sort
select mod(level - 1100) as c1,
       rpad(to_char(level), 1000'X') as c2
from dual
connect by level <= 1000;
 
commit;

 

 

인덱스 생성 및 통계 정보 수집(c1 컬럼으로 정렬된 인덱스를 생성함)

1
2
3
SQL>
create index ix1_t_batch_sort on t_batch_sort(c1);
exec dbms_stats.gather_table_stats(user, 'T_BATCH_SORT', cascade => true);

 

 

배치 io 비활성화 후 별도 테이블에 삽입
(ix1_t_batch_sort 인덱스를 이용하기 때문에 c1 컬럼값으로 정렬된채로 데이터가 들어감)

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
SQL>
drop table t_batch_off purge;
 
alter system flush buffer_cache;
 
alter session set "_optimizer_batch_table_access_by_rowid" = false;
 
create table t_batch_off as
select /*+ index(t ix1_t_batch_sort)
           no_batch_table_access_by_rowid(t) */
       rownum as rn,
       t.c1,
       substr(t.c2, 16) as c2,
       t.rowid as rid
from t_batch_sort t
where t.c1 >= 0;
 
SELECT * FROM DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST -rows -Projection +HINT_REPORT +outline');
 
Plan hash value: 1215737383
 
------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name             | Starts | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | CREATE TABLE STATEMENT        |                  |      1 |      0 |00:00:00.03 |    1063 |    171 |      5 |       |       |          |
|   1 |  LOAD AS SELECT               | T_BATCH_OFF      |      1 |      0 |00:00:00.03 |    1063 |    171 |      5 |  1042K|  1042K| 1042K (0)|
|   2 |   COUNT                       |                  |      1 |   1000 |00:00:00.01 |    1003 |    160 |      0 |       |       |          |
|   3 |    TABLE ACCESS BY INDEX ROWID| T_BATCH_SORT     |      1 |   1000 |00:00:00.01 |    1003 |    160 |      0 |       |       |          |
|*  4 |     INDEX RANGE SCAN          | IX1_T_BATCH_SORT |      1 |   1000 |00:00:00.01 |       3 |      8 |      0 |       |       |          |
------------------------------------------------------------------------------------------------------------------------------------------------

배치 io 비활성화 시 buffer를 1003, reads를 160 소모함

 

 

배치 io 활성화 후 별도 테이블에 삽입

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
SQL>
drop table t_batch_on purge;
 
alter system flush buffer_cache;
 
alter session set "_optimizer_batch_table_access_by_rowid" = true;
 
create table t_batch_on as
select /*+ index(t ix1_t_batch_sort)
           batch_table_access_by_rowid(t) */
       rownum as rn,
       t.c1,
       substr(t.c2, 16) as c2,
       t.rowid as rid
from t_batch_sort t
where t.c1 >= 0;
 
SELECT * FROM DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST -rows -Projection +HINT_REPORT +outline');
 
Plan hash value: 1927831086
 
--------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name             | Starts | A-Rows |   A-Time   | Buffers | Reads  | Writes |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | CREATE TABLE STATEMENT                |                  |      1 |      0 |00:00:00.01 |     872 |    166 |      5 |       |       |          |
|   1 |  LOAD AS SELECT                       | T_BATCH_ON       |      1 |      0 |00:00:00.01 |     872 |    166 |      5 |  1042K|  1042K| 1042K (0)|
|   2 |   COUNT                               |                  |      1 |   1000 |00:00:00.01 |     812 |    155 |      0 |       |       |          |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T_BATCH_SORT     |      1 |   1000 |00:00:00.01 |     812 |    155 |      0 |       |       |          |
|*  4 |     INDEX RANGE SCAN                  | IX1_T_BATCH_SORT |      1 |   1000 |00:00:00.01 |       3 |      8 |      0 |       |       |          |
--------------------------------------------------------------------------------------------------------------------------------------------------------

배치 io 활성화 시 buffer를 812, reads를 155 소모함

 

 

batch io 비활성화시 데이터 순서 확인

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
SQL>
select rn, c1, c2, rid
from t_batch_off
where rn between 1 and 20
order by rn;
 
        RN         C1 C2           RID
---------- ---------- ------------ ------------------
         1          0 1XXXXX       AAAP8VAAEAADZy+AAA
         2          0 101XXX       AAAP8VAAEAADZzKAAC
         3          0 201XXX       AAAP8VAAEAADZzZAAE
         4          0 301XXX       AAAP8VAAEAADZzkAAG
         5          0 401XXX       AAAP8VAAEAADZz0AAB
         6          0 501XXX       AAAP8VAAEAADa+DAAD
         7          0 601XXX       AAAP8VAAEAADa+WAAF
         8          0 701XXX       AAAP8VAAEAADa+mAAA
         9          0 801XXX       AAAP8VAAEAADa+1AAC
        10          0 901XXX       AAAP8VAAEAADbBbAAE
        11          1 2XXXXX       AAAP8VAAEAADZy+AAB
        12          1 102XXX       AAAP8VAAEAADZzKAAD
        13          1 202XXX       AAAP8VAAEAADZzZAAF
        14          1 302XXX       AAAP8VAAEAADZzpAAA
        15          1 402XXX       AAAP8VAAEAADZz0AAC
        16          1 502XXX       AAAP8VAAEAADa+DAAE
        17          1 602XXX       AAAP8VAAEAADa+WAAG
        18          1 702XXX       AAAP8VAAEAADa+mAAB
        19          1 802XXX       AAAP8VAAEAADa+1AAD
        20          1 902XXX       AAAP8VAAEAADbBbAAF

c1 컬럼이 정상적으로 정렬되어서 들어가 있음

 

 

batch io 비활성화시 데이터 순서 확인

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
SQL>
select rn, c1, c2, rid
from t_batch_on
where rn between 1 and 20
order by rn;
 
        RN         C1 C2           RID
---------- ---------- ------------ ------------------
         1          0 1XXXXX       AAAP8VAAEAADZy+AAA
         2          0 101XXX       AAAP8VAAEAADZzKAAC
         3          1 2XXXXX       AAAP8VAAEAADZy+AAB
         4          1 102XXX       AAAP8VAAEAADZzKAAD
         5          2 3XXXXX       AAAP8VAAEAADZy+AAC
         6          2 103XXX       AAAP8VAAEAADZzKAAE
         7          2 503XXX       AAAP8VAAEAADa+DAAF
         8          0 201XXX       AAAP8VAAEAADZzZAAE
         9          1 202XXX       AAAP8VAAEAADZzZAAF
        10          2 203XXX       AAAP8VAAEAADZzZAAG
        11          0 301XXX       AAAP8VAAEAADZzkAAG
        12          0 401XXX       AAAP8VAAEAADZz0AAB
        13          1 402XXX       AAAP8VAAEAADZz0AAC
        14          2 403XXX       AAAP8VAAEAADZz0AAD
        15          0 501XXX       AAAP8VAAEAADa+DAAD
        16          1 502XXX       AAAP8VAAEAADa+DAAE
        17          0 601XXX       AAAP8VAAEAADa+WAAF
        18          1 602XXX       AAAP8VAAEAADa+WAAG
        19          0 701XXX       AAAP8VAAEAADa+mAAA
        20          1 702XXX       AAAP8VAAEAADa+mAAB

c1 컬럼 정렬이 틀어져 있음

 

 

결과 비교 :
Batch I/O를 비활성화한 경우 TABLE ACCESS BY INDEX ROWID 방식(일반 방식)으로 테이블에 접근했고 C1 인덱스의 키 순서대로 결과가 나옴
원본 테이블 접근 구간인 Id 3을 기준으로 Buffers는 1003, Reads는 160이 발생함
Batch I/O를 활성화한 경우 TABLE ACCESS BY INDEX ROWID BATCHED 방식으로 테이블에 접근했고 C1 값이 인덱스 키 순서와 다르게 나옴
원본 테이블 접근 구간인 Id 3을 기준으로 Buffers는 812, Reads는 155가 발생함
두 실행 모두 하위 INDEX RANGE SCAN의 Buffers는 3으로 동일했지만 TABLE ACCESS 시 Buffers가 1003에서 812로 감소함(Reads는 160에서 155로 소폭 감소함)

 

 

결론 :
Batch I/O가 비활성화된 경우 이번 테스트에서는 C1 인덱스의 키 순서대로 결과가 출력됨
반면 Batch I/O가 활성화된 경우에는 일부 ROWID를 모아 테이블 블록 순서로 접근하면서 C1 값이 인덱스 키 순서와 다르게 출력됨
오라클 공식 가이드로 ORDER BY가 없는 SQL은 원래 결과 순서를 보장하지 않는다고 명시하고 있기 때문에
인덱스를 사용한다는 이유만으로 결과가 정렬될 것으로 생각하면 안되고 정렬된 결과가 필요한 경우 ORDER BY를 명시해줘야 함
성능 측면에서는 Batch I/O 활성화 후 TABLE ACCESS 계층의 Buffers가 1003에서 812로 감소했고 Reads도 160에서 155로 소폭 감소함
이를 통해 CF가 좋지 않은 인덱스를 이용해 테이블에 접근하는 경우 BATCHED 방식이 같은 블록에 대한 반복 접근을 줄여 버퍼 사용량과 I/O 효율을 개선할 수 있음을 확인함

 

 

참조 : 

https://docs.oracle.com/en/database/oracle/oracle-database/19/tgsql/optimizer-access-paths.html#GUID-720EA54F-AB65-4379-99A3-CAE166590127
오라클 19c 병렬 ITAS를 이용한 Clustering Factor 개선 테스트 ( https://positivemh.tistory.com/1399 )
오라클 19c Prefetch, Batch I/O, Table access by rowid batched 설명 ( https://positivemh.tistory.com/1026 )
https://hrjeong.tistory.com/201
https://hrjeong.tistory.com/213