h3.IOT란?
SQL> create table index_org_t (a number primary key, b varchar(10)) organization index;
SQL> create table heap_org_t (a number primary key, b varchar(10)) organization heap;
h4.IOT의 장점과 단점
h3.IOT, 언제 사용할 것인가?
h4.크기가 작고 NL 조인으로 반복 Lookup 하는 테이블
h4.폭이 좁고 긴(=로우 수가 많은)테이블
h4.넓은 범위를 주로 검색하는 테이블
비트맵 인덱스에 대한 오해
성별처럼 Distinct value 개수가 적은 컬럼으로 조회할 때 비트맵 인덱스를 사용하면 빠르다고 생각 하는 사람들이 많다. 즉, 넓은 범위를 조회할 때 B*Tree 인덱스보다 성능을 크게 향상시켜 준다는 얘긴데 과연 그럴까?
비트맵 인덱스의 저장 효율이 좋은 것은 사실이지만 조회 성능이 그다지 좋지는 않다. 테이블 Random 액세스 발생 측면에서는 B*Tree 인덱스와 똑같기 때문이며, 스캔할 인덱스 블록이 줄어드는 정도의 이점만 생긴다.
하나의 비트맵 인덱스 단독으로는 쓰임새가 별로 없다. 비트맵 인덱스가 가진 여러가지 특징(특히, 용량이 작고 여러 인덱스를 동시에 사용할 수 있다는 특징) 때문에 읽기 위주의 대용량 DW환경에 적합한 것일 뿐 대용량 데이터 조회에 유리한 것은 아니다.
h4.데이터 입력과 조회 패턴이 서로 다른 테이블
상이다.
select substr(일자, 1, 6) 월도, sum(판매금액) 총판매금액, avg(판매금액) 평균판매금액
from 영업실적
where 사번 = 's1234'
and 일자 between '20090101' and '20091231'
group by substr(일자, 1, 6)
Create table 영업실적(사번 varchar2(5), 일자 varchar2(8), ...
Constraint 영업실적_PK primary key (사번, 일자) ) organization index;
h3.Partitioned IOT
select 거래일자,지점번호,계좌번호,sum(거래량),sum(거래금액)
from 일별상품별계좌별거래
where 상품번호 = 'P7006050009'
and 거래일자 between '20080101' and '20080630'
group by 거래일자, 지점번호, 계좌번호
select 거래일자,지점번호,계좌번호,sum(거래량),sum(거래금액)
from 일별상품별계좌별거래
where 거래일자 between '20080101' and '20080630'
group by 거래일자, 지점번호, 계좌번호
h3.Overflow 영역
h3.Secondary 인덱스
h4.MS-SQL 서버의 비클러스터형 인덱스 진화 과정
h4.오라클 Logical rowid
Logical Rowid = PK + Physical Guess
h4.PCT_DIRECT_ACCESS
Alter index iot_second_idx REBUILD;
Alter index iot_second_idx UPDATE BLOCK REFERENCES;
create table t1(
c1 number not null
, c2 number
, c3 number
, c4 number
, constraint t1_pk primary key (c1) )
organization index ; -- IOT 생성
create index t1_x1 on t1 (c2) ; -- Secondary 인덱스 생성
SQL> insert into t1
2 select rownum, rownum, rownum, rownum
3 from all_objects
4 where rownum <= 1000;
1000 개의 행이 만들어졌습니다.
SQL> commit;
커밋이 완료되었습니다.
SQL> select index_name, PCT_DIRECT_ACCESS
2 from user_indexes
3 where index_name = 'T1_X1';
INDEX_NAME PCT_DIRECT_ACCESS
------------------------------ -----------------
T1_X1 100
SQL> exec dbms_stats.gather_index_stats(user, 't1_x1');
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> select index_name, PCT_DIRECT_ACCESS
2 from user_indexes
3 where index_name = 'T1_X1';
INDEX_NAME PCT_DIRECT_ACCESS
------------------------------ -----------------
T1_X1 64
SQL> alter index t1_x1 UPDATE BLOCK REFERENCES;
인덱스가 변경되었습니다.
SQL> select index_name, PCT_DIRECT_ACCESS
2 from user_indexes
3 where index_name = 'T1_X1';
INDEX_NAME PCT_DIRECT_ACCESS
------------------------------ -----------------
T1_X1 64
SQL> exec dbms_stats.gather_index_stats(user, 't1_x1');
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> select index_name, blevel, PCT_DIRECT_ACCESS
2 from user_indexes
3 where index_name = 'T1_X1';
INDEX_NAME BLEVEL PCT_DIRECT_ACCESS
------------------------------ ---------- -----------------
T1_X1 1 100
PCT_DIRECT_ACCESS에 대한 오해
인덱스 통게상 PCT_DIRECT_ACCESS값이 일정 비율 이하로 떨어지면, physical guess로 탐색했다가 PK로 다시 IOT를 스캔하는 비율이 높아져 성능이 떨어진다고 설명한 글이 있는데, 이 값이 100% 미만이면 오라클은 PK를 이용해 바로 IOT를 탐색한다. 오히려 휘발성 IOT에서 이 값이 100%를 가리킬 때가 더 문제 일 수 있다.
h4.비휘발성 IOT에 대한 Secondary 인덱스 튜닝 방안
exec dbms_stats.set_index_stats (user, 't1_x1', guessq => 100);
h4.휘발성 IOT에 대한 Secondary 인덱스 튜닝 방안
h4.Right-Growing IOT에서 pct_direct_access가 100미만으로 떨어지는 이유
T1 테이블에 값을 1부터 1,000까지 차례로 입력했기 때문에 기존 레코드 위치가 바뀔 이유가 없다. 그런데 왜 secondary 인덱스의 pct_direct_access 값이 64로 떨어졌는가?
h4.IOT_REDUNDANT_PKEY_ELIM
SQL> create table emp_iot
2 ( empno, ename, job, mgr, hiredate, sal, comm, deptno
3 , constraint pk_emp_iot primary key ( empno ) )
4 organization index
5 as
6 select * from scott.emp;
테이블이 생성되었습니다.
SQL> create index iot_secondary_index on emp_iot( ename );
인덱스가 생성되었습니다.
SQL> set autotrace traceonly explain;
SQL> select /*+ index(emp_iot iot_secondary_index) */ * from emp_iot where ename
= 'SMITH';
Execution Plan
----------------------------------------------------------
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 87 | 8 (0)|
|* 1 | INDEX UNIQUE SCAN| PK_EMP_IOT | 1 | 87 | 8 (0)|
|* 2 | INDEX RANGE SCAN| IOT_SECONDARY_INDEX | 1 | | 1 (0)|
------------------------------------------------------------------------------
SQL> select /*+ index(emp_iot iot_secondary_index) */ empno from emp_iot where
name = 'SMITH';
Execution Plan
----------------------------------------------------------
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 20 | 1 (0)|
|* 1 | INDEX RANGE SCAN| IOT_SECONDARY_INDEX | 1 | 20 | 1 (0)|
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("ENAME"='SMITH')
Note
-----
- 'PLAN_TABLE' is old version
h3.인덱스 클러스터 테이블
SQL> create cluster c_deptno# ( deptno number(2) ) index ;
클러스터가 생성되었습니다.
SQL> create index i_deptno# on cluster c_deptno#;
인덱스가 생성되었습니다.
SQL> create table emp1
2 cluster c_deptno# (deptno)
3 as
4 select * from scott.emp;
테이블이 생성되었습니다.
SQL> create table dept1
2 cluster c_deptno# (deptno)
3 as
4 select * from scott.dept;
테이블이 생성되었습니다.
SQL> select owner, table_name from dba_tables where cluster_name = 'C_DEPTNO#';
OWNER TABLE_NAME
------------------------------ ------------------------------
SCOTT DEPT1
SCOTT EMP1
SQL> break on deptno skip 1;
SQL> select d.deptno, e.empno, e.ename
2 , dbms_rowid.rowid_block_number(d.rowid) dept_block_no
3 , dbms_rowid.rowid_block_number(e.rowid) emp_block_no
4 from dept1 d, emp1 e
5 where e.deptno = d.deptno
6 order by d.deptno;
DEPTNO EMPNO ENAME DEPT_BLOCK_NO EMP_BLOCK_NO
---------- ---------- ---------- ------------- ------------
10 7839 KING 2352 2352
7782 CLARK 2352 2352
7934 MILLER 2352 2352
20 7902 FORD 2350 2350
7876 ADAMS 2350 2350
7788 SCOTT 2350 2350
7369 SMITH 2350 2350
7566 JONES 2350 2350
30 7900 JAMES 2351 2351
7844 TURNER 2351 2351
7698 BLAKE 2351 2351
7654 MARTIN 2351 2351
7499 ALLEN 2351 2351
7521 WARD 2351 2351
h4.인덱스 클러스터는 넓은 범위를 검색할 때 유리
SQL> create cluster objs_cluster# ( object_type VARCHAR2(19) ) index ;
클러스터가 생성되었습니다.
SQL> create index objs_cluster_idx on cluster objs_cluster#;
인덱스가 생성되었습니다.
SQL> create table objs_cluster -- 클러스터 테이블
2 cluster objs_cluster# ( object_type )
3 as
4 select * from all_objects
5 order by dbms_random.value ;
테이블이 생성되었습니다.
SQL> create table objs_regular -- 일반 테이블
2 as
3 select * from objs_cluster
4 order by dbms_random.value;
테이블이 생성되었습니다.
SQL> create index objs_regular_idx on objs_regular(object_type);
인덱스가 생성되었습니다.
SQL> alter table objs_regular modify object_name null;
테이블이 변경되었습니다.
SQL> alter table objs_cluster modify object_name null;
테이블이 변경되었습니다.
select /*+ index(t objs_regular_idx) */ count(object_name)
from objs_regular t
where object_type = 'TABLE';
Rows Row Source Operation
------- ---------------------------------------------------
0 STATEMENT
1 SORT AGGREGATE (cr=622 pr=0 pw=0 time=4153 us)
1632 TABLE ACCESS BY INDEX ROWID OBJS_REGULAR (cr=622 pr=0 pw=0 time=8220 us)
1632 INDEX RANGE SCAN OBJS_REGULAR_IDX (cr=6 pr=0 pw=0 time=1678 us)(Object ID 53035)
-- B*Tree 인덱스와 Heap Table은 테이블을 1.763번 Random 액세스하는 동안 616(=622-6) 개의 블록 I/O가 발생
select count(object_name)
from objs_cluster t
where object_type = 'TABLE';
Rows Row Source Operation
------- ---------------------------------------------------
0 STATEMENT
1 SORT AGGREGATE (cr=21 pr=0 pw=0 time=943 us)
1632 TABLE ACCESS CLUSTER OBJS_CLUSTER (cr=21 pr=0 pw=0 time=1664 us)
1 INDEX UNIQUE SCAN OBJS_CLUSTER_IDX (cr=1 pr=0 pw=0 time=18 us)(Object ID 53031)
-- B*Tree Cluster 인덱스를 통해 클러스터 테이블을 액세스 했더니 Random 액세스는 단 1회만 발생하였고, 클러스터를 스캔하는 동안 20(=21-1)개의 블록 I/O가 발생하였다.
-- 클러스터 인덱스를 '=' 조건으로 액세스할 때는 항상 Unique Scan이 나타난다
h4.클러스터 테이블과 관련한 성능 이슈
Truncate cluster objs_cluster#;
Drop cluster objs_cluster# including tables;
h4.Size 옵션
SQL> show parameter block_size;
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_block_size integer 8192
SQL> drop cluster c_deptno# including tables;
클러스터가 삭제되었습니다.
SQL> create cluster emp_cluster# ( empno number(4) ) pctfree 0 size 2000 index ;
클러스터가 생성되었습니다.
SQL> create index emp_cluster_idx on cluster emp_cluster#;
인덱스가 생성되었습니다.
SQL> create table emp1
2 cluster emp_cluster# ( empno )
3 as
4 select * from scott.emp;
테이블이 생성되었습니다.
SQL> select emp.empno, emp.ename, dbms_rowid.rowid_block_number(rowid) block_no
2 from emp1 emp;
EMPNO ENAME BLOCK_NO
---------- ---------- ----------
7902 FORD 628
7934 MILLER 628
7369 SMITH 630
7499 ALLEN 630
7521 WARD 630
7566 JONES 630
7654 MARTIN 631
7698 BLAKE 631
7782 CLARK 631
7788 SCOTT 631
7839 KING 632
7844 TURNER 632
7876 ADAMS 632
7900 JAMES 632
SQL> drop table emp1 purge;
테이블이 삭제되었습니다.
SQL> create table emp1
2 cluster emp_cluster# ( empno )
3 as
4 select empno, ename, lpad('*', 970) data -- 한 로우가 1000 바이트쯤 되도록
5 from scott.emp,(select rownum no from dual connect by level <= 10)
6 where empno = 7900;
테이블이 생성되었습니다.
SQL> select empno, ename, dbms_rowid.rowid_block_number(rowid) block_no
2 from emp1 ;
EMPNO ENAME BLOCK_NO
---------- ---------- ----------
7900 JAMES 630
7900 JAMES 630
7900 JAMES 632
7900 JAMES 632
7900 JAMES 632
7900 JAMES 632
7900 JAMES 632
7900 JAMES 632
7900 JAMES 632
7900 JAMES 632
SQL> create cluster username_cluster# ( username varchar2(30) )
2 hashkeys 100 size 50;
클러스터가 생성되었습니다.
SQL> create table user_cluster
2 cluster username_cluster# ( username )
3 as
4 select * from all_users;
테이블이 생성되었습니다.
SQL> create table user_regular as select * from all_users;
테이블이 생성되었습니다.
SQL> create unique index user_regular_idx on user_regular(username);
인덱스가 생성되었습니다.
SQL> alter table user_regular modify user_id null;
테이블이 변경되었습니다.
SQL> alter table user_cluster modify user_id null;
테이블이 변경되었습니다.
SQL> alter session set sql_trace = true;
세션이 변경되었습니다.
SQL> declare
2 l_user_id user_regular.user_id%type;
3 begin
4 for c in (select owner from objs_regular where owner <> 'PUBLIC')
5 loop
6 select user_id into l_user_id from user_regular where username = c.owne
r;
7 select user_id into l_user_id from user_cluster where username = c.owne
r;
8 end loop;
9 end;
10 /
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> alter session set sql_trace = false;
세션이 변경되었습니다.
SELECT USER_ID
FROM
USER_REGULAR WHERE USERNAME = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 29856 0.35 0.41 0 0 0 0
Fetch 29856 0.34 0.30 0 59712 0 29856
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 59713 0.70 0.72 0 59712 0 29856
Rows Row Source Operation
------- ---------------------------------------------------
29856 TABLE ACCESS BY INDEX ROWID USER_REGULAR (cr=59712 pr=0 pw=0 time=351640 us)
29856 INDEX UNIQUE SCAN USER_REGULAR_IDX (cr=29856 pr=0 pw=0 time=202659 us)(object id 53043)
SELECT USER_ID
FROM
USER_CLUSTER WHERE USERNAME = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 29856 0.26 0.33 0 0 0 0
Fetch 29856 0.42 0.25 0 29856 0 29856
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 59713 0.68 0.59 0 29856 0 29856
Rows Row Source Operation
------- ---------------------------------------------------
29856 TABLE ACCESS HASH USER_CLUSTER (cr=29856 pr=0 pw=0 time=225642 us)
h3.IOT와 클러스터 테이블을 동시에 적용한 튜닝 사례