Oracle/Administration2018. 6. 18. 14:29

UNIQUE 인덱스와 NON-UNIQUE 인덱스는 상이한 방식으로 블록을 저장하며, 이로 인한 오버헤드가 발생할 수 있다.

 

테스트를 위해 아래와 같이 테이블과 인덱스를 생성하자. 동일한 값을 가진 c1, c2 칼럼에 대해 각각 UNIQUE 인덱스(t1_u1)와 NON-UNIQUE 인덱스(t_x1)를 생성했다.

-- 1
DROP TABLE t1 PURGE;
CREATE TABLE t1 AS SELECT ROWNUM AS c1, ROWNUM AS c2 FROM XMLTABLE ('1 to 100000');

CREATE UNIQUE INDEX t1_u1 ON t1 (c1);
CREATE INDEX t1_x1 ON t1 (c2);

 

ANALYZE 문으로 인덱스의 구조를 검증해보자. 값을 비교하기 위해 임시 테이블에 통계정보를 삽입했다.

-- 2
ANALYZE INDEX t1_u1 VALIDATE STRUCTURE OFFLINE;

DROP TABLE t_index_stats PURGE;
CREATE TABLE t_index_stats AS SELECT * FROM index_stats;

ANALYZE INDEX t1_x1 VALIDATE STRUCTURE OFFLINE;

INSERT INTO t_index_stats SELECT * FROM index_stats;
COMMIT;

 

통계정보를 비교해보면 UNIQUE 인덱스인 t1_u1 인덱스보다 NON-UNIQUE 인덱스인 t1_x1 인덱스가 더 많은 공간을 사용한다는 사실을 알 수 있다. 리프 블록의 로우 길이가 1바이트씩 증가함에 따라 리프 블록의 개수가 14개로 늘었고, 리프 블록의 개수만큼 브랜치 블록의 로우도 14개가 늘었다. 이로 인해 브랜치 블록의 로우 길이도 늘어났다.

-- 3
SELECT   column_name, t1_u1, t1_x1, t1_x1 - t1_u1 AS diff
    FROM t_index_stats
 UNPIVOT (column_value
          FOR column_name IN (height
                            , blocks
                            , lf_rows
                            , lf_blks
                            , lf_rows_len
                            , lf_blk_len
                            , br_rows
                            , br_blks
                            , br_rows_len
                            , br_blk_len
                            , del_lf_rows
                            , del_lf_rows_len
                            , distinct_keys
                            , most_repeated_key
                            , btree_space
                            , used_space
                            , pct_used
                            , rows_per_key
                            , blks_gets_per_access
                            , pre_rows
                            , pre_rows_len
                            , opt_cmpr_count
                            , opt_cmpr_pctsave))
   PIVOT (MAX (column_value)
          FOR name IN ('T1_U1' AS t1_u1
                     , 'T1_X1' AS t1_x1))
ORDER BY 1;

COLUMN_NAME            T1_U1   T1_X1   DIFF
-------------------- ------- ------- ------
BLKS_GETS_PER_ACCESS       3       3      0
BLOCKS                   256     256      0
BR_BLKS                    1       1      0
BR_BLK_LEN              8032    8032      0
BR_ROWS                  207     221     14 -- !
BR_ROWS_LEN             2256    2630    374 -- !
BTREE_SPACE          1672032 1784032 112000
DEL_LF_ROWS                0       0      0
DEL_LF_ROWS_LEN            0       0      0
DISTINCT_KEYS         100000  100000      0
HEIGHT                     2       2      0
LF_BLKS                  208     222     14 -- !
LF_BLK_LEN              8000    8000      0
LF_ROWS               100000  100000      0
LF_ROWS_LEN          1488892 1588892 100000 -- !
MOST_REPEATED_KEY          1       1      0
OPT_CMPR_COUNT             0       0      0
OPT_CMPR_PCTSAVE           0       0      0
PCT_USED                  90      90      0
PRE_ROWS                   0       0      0
PRE_ROWS_LEN               0       0      0
ROWS_PER_KEY               1       1      0
USED_SPACE           1491148 1591522 100374

23 행이 선택되었습니다.

 

브랜치 노드는 고유한 값을 기준으로 리프 노드를 분기해야 한다. 중복 값이 존재할 수 있는 NON-UNIQUE 인덱스는 브랜치 노드의 고유 값을 만들기 위해 ROWID를 칼럼 값으로 처리한다. 이로 인해 브랜치 노드에 ROWID가 저장될 수 있다. 그래서 더 많은 저장 공간을 사용한 것이다. 블록 덤프를 통해 저장 방식을 확인해보자. 아래는 t1_u1 인덱스의 블록 덤프 내용이다. col 0에 c1 칼럼 값이 저장되어 있다. 리프 블록의 로우 길이는 11이고, 제한 ROWID(01 c0 00 bb 00 00)가 함께 저장된 것을 확인할 수 있다.

-- 4-1 : T1_U1
Branch block dump
=================
...
row#0[8052] dba: 29494981=0x1c20ec5
col 0; len 3; (3):  c2 06 16        -->  521
row#1[8044] dba: 29494982=0x1c20ec6
col 0; len 3; (3):  c2 0b 23        --> 1034
row#2[8036] dba: 29494983=0x1c20ec7
col 0; len 3; (3):  c2 10 30        --> 1547
...

Leaf block dump
===============
...
row#0[8025] flag: -------, lock: 0, len=11, data:(6):  01 c0 00 bb 00 00
col 0; len 2; (2):  c1 02 --> 1
row#1[8014] flag: -------, lock: 0, len=11, data:(6):  01 c0 00 bb 00 01
col 0; len 2; (2):  c1 03 --> 2
row#2[8003] flag: -------, lock: 0, len=11, data:(6):  01 c0 00 bb 00 02
col 0; len 2; (2):  c1 04 --> 3
...

 

아래는 t1_x1 인덱스의 블록 덤프 내용이다. 브랜치 블록과 리프 블록에 col 1이 추가된 것을 확인할 수 있다. 브랜치 블록은 TERM, 리프 블록은 제한 ROWID 값이 저장되어 있다. TERM은 해당 칼럼 값이 없더라도 값을 고유하게 구분할 수 있음을 나타낸다. 이로 인해 리프 블록의 로우 길이가 12로 늘어난 것이다. 고유한 값의 열 조합으로 NON-UNIQUE 인덱스를 생성하면 오버헤드가 발생할 수 있음을 알 수 있다.

-- 4-2 : T1_X1
Branch block dump
=================
...
ow#0[8051] dba: 29495405=0x1c2106d
col 0; len 3; (3):  c2 05 57        -->  486
col 1; TERM
row#1[8042] dba: 29495406=0x1c2106e
col 0; len 3; (3):  c2 0a 42        -->  965
col 1; TERM
row#2[8033] dba: 29495407=0x1c2106f
col 0; len 3; (3):  c2 0f 2d        --> 1444
col 1; TERM
...

Leaf block dump
===============
...
row#0[8024] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 02 --> 1
col 1; len 6; (6):  01 c0 00 bb 00 00 --> ROWID
row#1[8012] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 03 --> 2
col 1; len 6; (6):  01 c0 00 bb 00 01 --> ROWID
row#2[8000] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 04 --> 3
col 1; len 6; (6):  01 c0 00 bb 00 02 --> ROWID
...

 

아래 쿼리로 테이블의 ROWID와 블록 덤프에 저장된 값을 비교해볼 수 있다. 첫 번째 열의 ROWID 덤프 값(1,c0,0,bb,0,0)과 블록 덤프 값(01 c0 00 bb 00 00)이 일치하는 것을 확인할 수 있다. ROWID의 앞쪽 8바이트는 오브젝트 ID를 의미한다.

-- 5-1
SELECT c1, ROWID, DUMP (ROWID, 16) AS rowid_dump FROM t1 WHERE c1 <= 3;

C1 ROWID              ROWID_DUMP
-- ------------------ --------------------------------------
 1 AAA5b8AAHAAAAC7AAA Typ=69 Len=10: 0,3,96,fc,1,c0,0,bb,0,0 -- 0,3,96,fc + 1,c0,0,bb,0,0
 2 AAA5b8AAHAAAAC7AAB Typ=69 Len=10: 0,3,96,fc,1,c0,0,bb,0,1
 3 AAA5b8AAHAAAAC7AAC Typ=69 Len=10: 0,3,96,fc,1,c0,0,bb,0,2

3 행이 선택되었습니다.

-- 5-2
SELECT object_id, TO_CHAR (object_id, 'XXXXXX') AS object_id_hex
  FROM user_objects
 WHERE object_name = 'T1';

OBJECT_ID OBJECT_HEX
--------- ----------
   235260   396FC

1개의 행이 선택되었습니다.

 

t1_x1 인덱스는 브랜치 블록에 ROWID가 저장되지 않았다. c1 칼럼 값이 고유하기 때문이다. 브랜치 블록에 ROWID가 저장되는 예제를 살펴보자. 테스트를 위해 아래와 같이 테이블과 인덱스를 생성하자.

-- 6
DROP TABLE t2 PURGE;
CREATE TABLE t2 AS SELECT 'A' AS c1 FROM XMLTABLE ('1 to 2000');
CREATE INDEX t2_x1 ON t2 (c1);

 

아래는 t2_x1 인덱스의 트리 덤프 내용이다. 4개의 리프 블록으로 구성되며, 첫 번째 리프 블록에 553개의 행이 저장된 것을 확인할 수 있다.

-- 7
----- begin tree dump
branch: 0x1c20cfb 29494523 (0: nrow: 4, level: 1)
   leaf: 0x1c20cfc 29494524 (-1: row:552.552 avs:824)
   leaf: 0x1c20cfd 29494525 (0: row:552.552 avs:824)
   leaf: 0x1c20cfe 29494526 (1: row:552.552 avs:824)
   leaf: 0x1c20cff 29494527 (2: row:344.344 avs:3528)
----- end tree dump

 

아래는 t2_x1 인덱스의 브랜치 블록 덤프 내용이다. col 1에 TERM이 아닌 ROWID가 저장된 것을 확인할 수 있다. c1 칼럼 값이 모두 A로 중복되므로 ROWID를 통해 b-tree 노드를 분기한 것이다.

-- 8
Branch block dump
=================
...
row#0[8047] dba: 29494525=0x1c20cfd
col 0; len 1; (1):  41 --> A
col 1; len 6; (6):  01 c1 f3 93 02 28 --> ROWID
row#1[8034] dba: 29494526=0x1c20cfe
col 0; len 1; (1):  41 --> A
col 1; len 6; (6):  01 c1 f3 94 01 bf --> ROWID
row#2[8021] dba: 29494527=0x1c20cff
col 0; len 1; (1):  41 --> A
col 1; len 6; (6):  01 c1 f3 95 01 56 --> ROWID

 

아래 쿼리에서 553번째 행의 ROWID 덤프 값((1,c1,f3,93,2,28)이 첫 번째 브랜치 블록의 col 1 값(01 c1 f3 93 02 28)과 일치하는 것을 확인할 수 있다. 브랜치 블록은 리프 블록의 마지막 값을 저장한다.

-- 9
SELECT *
  FROM (SELECT ROWNUM AS rn, DUMP (ROWID, 16) AS rowid_dump FROM t2)
 WHERE rowid_dump LIKE '%1,c1,f3,93,2,28';

 RN ROWID_DUMP
--- ----------------------------------------
553 Typ=69 Len=10: 0,3,97,e2,1,c1,f3,93,2,28 -- 0,3,97,e2 + 1,c1,f3,93,2,28

1개의 행이 선택되었습니다.


'Oracle > Administration' 카테고리의 다른 글

CLOB #2 - NVL 함수  (0) 2018.06.23
CLOB #1 - BASICFILE vs SECUREFILE  (0) 2018.06.23
오라클 에러 조회 함수  (0) 2018.03.04
오라클 에러 테이블  (0) 2018.03.01
PK 제약조건과 인덱스 #2  (0) 2014.05.06
Posted by 정희락_