태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

인트라 블록 로우 체이닝(Intra-Block Row Chaining)

오라클 2010.08.02 16:13
로우 체이닝이라고 불리는 현상은 정확하게 구분하면 로우 체이닝(Row Chaining)로우 마이그레이션(Row Migration)으로 나뉩니다.

여기까지는 일반적으로 알려져 있습니다. 한가지 자주 언급되지 않는 현상이 인트라 블록 로우 체이닝(Intra-Block Row Chaining)입니다. 말 그대로 같은 블록 내에서 로우 체이닝이 발생한다는 것을 의미합니다.

블록 레벨에서(딕셔너리 레벨이 아닌) 하나의 로우가 저장할 수 있는 최대 컬럼 수는 255개입니다. 이 제약은 테이블이 255개 이상의 컬럼을 저장하지 못했던 먼 옛날의 버전의 오라클에서 생긴 제약입니다. 이 제약이 어떤 이유로 인해(아마도 구현상의 문제 때문에) 완전히 해소되지 못하고 인트라 블록 로우 체이닝을 일으키는 원인이 되는 것입니다.

간단한 테스트 케이스를 통해 상세한 논의를 해보겠습니다.

(단, 그 전에 한가지 전제가 필요하겠습니다. 오라클은 특정 로우를 읽으러 갔을 때 체이닝때문에 나머지 부분을 읽으러 가야할 경우 V$SES[SYS]STAT 뷰의 table fetch contined row 항목 값을 증가시킵니다. 따라서 이 항목값을 모니터링하면 로우 체이닝의 발생 여부를 간접적으로 모니터링할 수 있습니다.)

1. 오라클 버전은 11.1.0.6입니다.

SQL> select * from v$version where rownum = 1;

BANNER
-----------------------------------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production
2. 300개의 컬럼을 가지는 테이블 T_CHN2을 생성합니다. 편의를 위해서 모든 컬럼의 디폴트 값을 1로 지정합니다.
SQL> declare
  2  	v_sql		varchar2(20000);
  3  begin
  4  	v_sql := 'create table t_chn2 ('  || chr(10);
  5  
  6  	for idx in 1 .. 299 loop
  7  		v_sql := v_sql || 'c' || idx || ' number default 1, ' || chr(10);
  8  	end loop;
  9  
 10  	v_sql := v_sql || 'c300 number default 1)';
 11  
 12  	execute immediate v_sql;
 13  end;
 14  /

PL/SQL procedure successfully completed.
3. 총 1000건의 로우를 추가합니다. 컬럼 C1을 제외한 나머지 컬럼들은 디폴트 값이 1이 저장될 것입니다.
SQL> insert into t_chn2(c1)
  2  select
  3  	level
  4  from dual connect by level <= 1000;

1000 rows created.
4. 앞서 설명에 의하면 하나의 로우가 255개보다 많은 수의 컬럼으로 이루어진 경우에는 블록 내에서 체이닝이 발생하게 됩니다. 첫번째 블록에 대해 덤프를 수행해서 어떤 형태로 체이닝이 발생하는지 살펴 보겠습니다.
SQL> col fno new_value fno
SQL> col bno new_value bno
SQL> select
  2  	dbms_rowid.rowid_relative_fno(rowid) as fno,
  3  	dbms_rowid.rowid_block_number(rowid) as bno
  4  from t_chn2
  5  where c1 = 1;

       FNO        BNO
---------- ----------
         4       1216
아래 결과를 보면 재미있는 사실을 알 수 있습니다. 300개의 컬럼은 (나머지 255개의 컬럼 + 첫 45개의 컬럼)으로 체이닝된다는 것입니다. fg(flag byte)값이 L=Last Piece, H+F=Header+First Piece인 것으로부터 추측할 수 있습니다.
(블록 덤프을 간편하게 수행하기 위해 티팩을 사용합니다)
SQL> select * from table(tpack.block_dump(&fno, &bno, 0));
...
tab 0, row 0, @0x1c80
tl: 768 fb: -----L-- lb: 0x1  cc: 255  <-- 첫번째 로우의 나머지 255개 컬럼
col  0: [ 2]  c1 02
col  1: [ 2]  c1 02
...
col 254: [ 2]  c1 02
tab 0, row 1, @0x1bf0
tl: 144 fb: --H-F--- lb: 0x1  cc: 45   <-- 첫번째 로우의 첫 45개 컬럼
nrid:  0x010004c0.0
col  0: [ 2]  c1 02
col  1: [ 2]  c1 02
...
col 44: [ 2]  c1 02
tab 0, row 2, @0x18f0
tl: 768 fb: -----L-- lb: 0x1  cc: 255   <-- 두번째 로우의 나머지 255개 컬럼
col  0: [ 2]  c1 02
col  1: [ 2]  c1 02
...
col 254: [ 2]  c1 02
tab 0, row 3, @0x1860
tl: 144 fb: --H-F--- lb: 0x1  cc: 45   <-- 두번째 로우의 첫 45개 컬럼
nrid:  0x010004c0.2
col  0: [ 2]  c1 03
col  1: [ 2]  c1 02
...
5. (나머지 255개의 컬럼 + 첫 45개의 컬럼)로 체이닝이 이루어져 있으므로 첫 45개의 컬럼을 읽을 때까지는 체이닝에 의해 table fetch contined row 항목 값이 증가하는 현상이 발생하지 않은 것입니다.
(V$SESSTAT 뷰를 간편하게 조회하기 위해 역시 티팩을 사용합니다)
SQL> -- for table full scan(45th column)
SQL> exec tpack.begin_session_snapshot;

PL/SQL procedure successfully completed.

SQL> 
SQL> select /*+ full(t_chn2) */ count(c45) from t_chn2;

COUNT(C45)
----------
      1000

SQL> exec tpack.add_session_snapshot;

PL/SQL procedure successfully completed.

SQL> select * from table(tpack.session_snapshot_report)
  2  where type = 'STAT' and item like 'table fetch continued row'
  3  ;

no rows selected
반면에 46번째 컬럼을 액세스하게 되면 table fetch contined row 항목 값이 증가하게 됩니다. 불필요한 컬럼을 액세스하지 않아야 할 이유 중의 하나가 될 수 있겠습니다.
SQL> -- for table full scan (46th column)
SQL> exec tpack.begin_session_snapshot;

PL/SQL procedure successfully completed.

SQL> select /*+ full(t_chn2) */ count(c46) from t_chn2;

COUNT(C46)
----------
      1000

SQL> exec tpack.add_session_snapshot;

PL/SQL procedure successfully completed.

SQL> select * from table(tpack.session_snapshot_report)
  2  where type = 'STAT' and item like 'table fetch continued row'
  3  ;

TYPE  ITEM                            START_VAL    END_VAL TOTAL_DELTA DELTAS
----- ------------------------------ ---------- ---------- ----------- -------
STAT  table fetch continued row            9214       9368         154 154
6. 테이블을 재구축(MOVE)하게 되면 어떻게 될까요? 로우 체이닝과 마찬가지로 인트라 블록 로우 체이닝또한 테이블을 재구축하는 것으로 해결되지 않을 것으로 추측할 수 있습니다. 단, 테이블 재구축에 의해 한가지 재미있는 현상이 발생합니다. 테이블을 재구축한 후 첫번째 블록을 덤프해보겠습니다.
SQL> -- after table move
SQL> alter table t_chn2 move;

Table altered.
재미있게도 300개의 컬럼이 (첫 255개의 컬럼 + 나머지 45개의 컬럼)으로 체이닝되어 있습니다. 테이블 재구축 과정에서 체이닝의 순서가 바뀌는 것을 알 수 있습니다.
SQL> -- get the 1st block(c1 = 1)
SQL> col fno new_value fno
SQL> col bno new_value bno
SQL> select
  2  	dbms_rowid.rowid_relative_fno(rowid) as fno,
  3  	dbms_rowid.rowid_block_number(rowid) as bno
  4  from t_chn2
  5  where c1 = 1;

       FNO        BNO
---------- ----------
         4       2120

SQL> select * from table(tpack.block_dump(&fno, &bno, 0));

tab 0, row 0, @0x1c7a
tl: 774 fb: --H-F--- lb: 0x0  cc: 255
nrid:  0x01000848.1
col  0: [ 2]  c1 25
col  1: [ 2]  c1 02
col  2: [ 2]  c1 02
...
col 254: [ 2]  c1 02
tab 0, row 1, @0x1bf0
tl: 138 fb: -----L-- lb: 0x0  cc: 45
col  0: [ 2]  c1 02
col  1: [ 2]  c1 02
...
그 덕분에 이번에는 Table Full Scan으로 255번째의 컬럼까지만 읽을 때는 table fetch contined row 항목 값이 증가하지 않습니다.
SQL> -- 255th column
SQL> exec tpack.begin_session_snapshot;

PL/SQL procedure successfully completed.

SQL> select /*+ full(t_chn2) */ count(c255) from t_chn2;

COUNT(C255)
-----------
       1000

SQL> exec tpack.add_session_snapshot;

PL/SQL procedure successfully completed.

SQL> select * from table(tpack.session_snapshot_report)
  2  where type = 'STAT' and item like 'table fetch continued row'
  3  ;

no rows selected
반면에 256번째의 컬럼을 액세스하는 순간 table fetch contined row 항목 값이 증가하게 됩니다.
SQL> -- 256th column
SQL> exec tpack.begin_session_snapshot;

PL/SQL procedure successfully completed.

SQL> select /*+ full(t_chn2) */ count(c256) from t_chn2;

COUNT(C256)
-----------
       1000

SQL> exec tpack.add_session_snapshot;

PL/SQL procedure successfully completed.

SQL> select * from table(tpack.session_snapshot_report)
  2  where type = 'STAT' and item like 'table fetch continued row'
  3  ;

TYPE  ITEM                            START_VAL    END_VAL TOTAL_DELTA DELTAS
----- ------------------------------ ---------- ---------- ----------- -------
STAT  table fetch continued row           10578      10720         142 142
아무리 간단한 기능이라고 하더라도 실제의 동작 방식은 의외로 복잡하고 다양하다는 것을 알 수 있습니다.

로우 체이닝에 의한 부가적인 읽기 작업은 평상시에는 큰 영향이 없다가 특정 상황에서 갑자기 성능 저하를 일으키기도 합니다. 버그가 개입하는 경우도 종종 있구요. 위에서 보는 것과 같은 분석 기법을 몸에 익히고 있다면 그런 상황에서 좀 더 유연하고 과학적으로 대처할 수 있을 것 입니다.

PS) 나중에 기회 있을 때 로우 체이닝/로우 마이그레이션/인트라 블록 로우 체이닝까지 포함해서 좀 더 종합적인 포스트를 올리도록 하겠습니다.

저작자 표시
신고
Trackback 0 : Comments 3
  1. Ejql 2010.08.03 13:57 신고 Modify/Delete Reply

    인트라 블록이라.. 투덜이스머프가 생각나게하는 넘입니다.
    잘 보고 갑니다.

  2. 2dong0 2010.08.04 11:02 신고 Modify/Delete Reply

    재미있는 책을 읽는거 같아서 기분 좋습니다. ^^좋은 정보 감사합니다.

Write a comment

티스토리 툴바