* B-tree 인덱스 설명은 http://wiki.oracleclub.com/pages/viewpage.action?pageId=1507450 에서 사진 및 내용을 인용하였습니다.^^
제2장 인덱스의 유형과 특징
인덱스란 말 그대로 전체 내용물 중에서 특정한 부분을 바로 찾을 수 있는 목차(目次)나 색인(索引)의 개념.
테이블에 저장되어있는 데이터를 좀 더 효율적이고 빠르게 찾기 위해 사용.
관계형 데이터베이스에서 적용하고 있는 인덱스 유형은 아래와 같음.
B-Tree 인덱스
비트맵(Bitmap) 인덱스
B-tree 클러스트 인덱스
해쉬(hash)클러스터 인덱스
리버스 키(Reverse key) 인덱스
비트맵 조인(Bitmap join) 인덱스
함수기반(Function-based) 인덱스
2.1 B-tree 인덱스
2.1.1 B-tree 인덱스의 구조
:——————————————————————————————————
range Scan일 경우
인덱스는 구성컬럼과 ROWID로 정렬되어 있다.
ROWID에는 로우가 저장된 블록의 주소, 로우의 슬롯번호가 기록되어 있다.
처리할 범위의 첫번째 인덱스로우를 브랜체블록을 통해 리프블록을 액세스한 다음 거기의 ROWID로 블록을 찾는다.
SGA부터 찾아보고 없으면 디스크에 액세스해서 복사본을 SGA에 복사한다.
새롭게 액세스한 데이터블록정보를 PGA로 가져와서 해당 SQL의 처리버퍼에 넣는다 (일명 원 버퍼).
인덱스블록을 액세스한 내용을 PGA버퍼에서 찾아 다른 조건까지 검증하여 성공한 로우만 운반단위로 보낸다 만약 클러스터링 팩터가 좋다면 여러건의 인덱스 스캔을 하나의 버퍼에서 처리할 수 있다.
PGA버퍼에서 찾을 수 없다면 다시 다른 블록을 SGA에 액세스하여 새로운 블럭을 담고 위의 처리를 반복수행한다.
리프블록에서 액세스한 로우가 처리범위를 넘으면 처리를 종료한다.
ROWID의 구성
:——————————————————————————————————
2.1.2 B-tree 인덱스의 조작(Operation)
가.인덱스 생성(Creation)
테이블을 액새스하여 정렬을 수행.
정렬된 결과를 인덱스 세그먼트의 리프 블록에 기록하기 시작.
리프블록이 PCTFREE에 도달하여 새로운 블록을 요구하게 되면 브랜치블록을 생성.
리프블록이 증가함에 따라 브랜치블록도 채워져 가다 블랜치블록이 PCTFREE에 도달하면 새로운 리프블록과 새로운 브랜치블록을 생성할때 루트블록이 생성된다.
새로운 브랜치블록이 만들어질 때마다 루트블록에 브랜치블록의 DBA정보를 기록한다.
인덱스 블록에 보다 많은 로우를 담기 위해 취할 수 있는방법
CREATE INDEX ord_customer_idx
ON orders (customer_id, sales_id)
COMPRESS 1;
:——————————————————————————————————
나.인덱스 블럭의 분할(SPILT)
마지막 값이 삽입되는 경우 : 그림처럼 PCTFREE에 도달한 블럭일 경우엔 새로운 블럭에 등록된다.
중간 값이 삽입되는 경우 : PCTFREE가 초과하게 되므로 분할이 발생하는데 어차피 2개의 블럭으로 수정되므로 2/3만 채우도록 하면서 양쪽을 모두 재편하게 됨(또 다른 중간 값이 들어와 계속 분할 되는 것을 방지하기 위함).
하지만 분할이 일어난 블럭에 새로운 값이 들어오지 않으면 저장공간 낭비를 초래하게 되고 이를 해결하기 위해선 인덱스를 재생성하는 방법뿐이다.
:——————————————————————————————————
다.데이터의 삭제 및 갱신
데이터를 삭제했을 경우 테이블의 로우는 제거되지만 인덱스의 로우는 삭제되었다는 표시(flag)만 추가된다.(저장공간낭비와 스캔시 액세스 블럭 증가)
한 리프 블럭이 모두 삭제 되었을 경우 브랜치 블럭에 해당 리프 블럭을 가리키는 로우도 삭제 표시가 된다.
갱신할 경우 삭제와 삽입이 발생.
이러한 이유로 수정이 빈번하지 않는 컬럼을 인덱스로 사용권장.
데이터처리(DML)가 많이 수행되는 테이블은 정기적으로 재생성을 할 필요가 있다.
:——————————————————————————————————
라.인덱스를 경유한 검색
1.루트 블록을 찾는다.
2.주어진 값보다 같거나 최소값을 찾는다.(찾는값 >= 인덱스값)
찾는값 < 인덱스 값 (Lmc에 있는 dba로 이동)
찾는 값 = 인덱스 값 (해당 dba로 이동)
찾는 값 > 인덱스 값
(검색 후 찾는 값 = 인데스 값이면 해당 dba로 이동 그렇지 않으면,
찾는 값 < 인데스 값을 만족하는 인덱스 최소값)
3.리프 블록을 찾을 때까지 ② 의 단계를 반복해서 수행.
4.리프 블록에서 주어진 값을 가진 키를 찾아 존재하면 ROWID를 이용해 테이블을 액세스하고,
그렇지 않으면 'No Data found'로 결과 반환 만약, col2의 조건을 'ACC'가 아닌 'AC%'로
바꾸면 Col1 = 'B'이면서 Col2 = 'AC'보다 같거나 큰 것에서 스캔을 시작, 'AD' 보다
작으면 테이블을 액세스하고, 그렇지 않으면 종료한다.
:——————————————————————————————————
마.B-tree인덱스의 문제점
B-TREE인덱스에서는 실제 컬럼 값을 인덱스에도 보관하고 있어야 한다는 점이 대용량 데이터를 관리할 때 부담이 된다.
B-TREE인덱스 컬럼값의 분포도가 좋아야 한다는 점
결합인덱스에서 조건을 사용하지 않는 컬럼이나 =조건이 아닌 컬럼이 결합인덱스 중간에 있으면 액세스효율성이 떨어진다는 점
다양한 액세스패턴을 수용하기 위해 많은 인덱스가 필요할 수 있다는 점
NOT이나 NULL을 사용하거나 복잡한 OR조건에서는 인덱스의 성능을 보장받지 못한다는 점
2.2 비트맵(Bitmap) 인덱스
2.2.1 비트맵(Bitmap) 인덱스의 탄생 배경
2.2.2 비트맵(Bitmap) 인덱스의 구조와 특성
구조
특성
비트를 저장할 때는 '선분 형태(Start rowid ~ End rowid)'로 저장을 하므로 일종의 압축 개념이 되며, 키 압축(Key Compression)이 적용되어 저장 공간이 매우 절약된다.
비트맵 연산을 통해 처리하므로 OR 연산이 가능하다.
NULL 값도 BIT를 구성하고 있어 NULL 및 NOT 조건검색이 가능하다.
수정이 빈번하게 발생하는 컬럼은 인덱스의 크기가 크게 증가하고 블록레벨 잠금(Block Level Locking)으로 인해 많은 부하가 유발될 수 있다.
데이터 웨어하우스 업무에 주로 활용된다.
» 데이터 웨어하우스 http://100.naver.com/100.nhn?docid=717310
생성절차
인덱스를 생성하고자 하는 컬럼의 값들을 찾기 위해 테이블 스캔을 한 후
bitmap generator에 의해 컬럼값, start rowid, end rowid , bitmap을 갖는 인덱스 엔트리를 생성한다.
2단계에서 생성된 Bitmap들을 B-tree구조에 넣기 쉽도록 key값과 start rowid 순으로 정렬한다.
마지막 단계에서는 정렬된 인덱스 엔트리들을 단순히 B-tree구조로 삽입한다.
제한사항
Partition table에서 Global Index에는 Bitmap Index를 만들 수 없다.
Local Index를 구성할 경우에는 Bitmap Index로 만들 수 있으나, 여러 파티션에 걸쳐있는 Global Index를 구성할 때는 Bitmap Index를 만들 수 없다.
Bitmap Index는 RBO(rule base optimizer) mode에서는 사용될 수 없다.
Bitmap Index는 통계치에 의하여 산출된 cost를 기반으로 Bitmap Index의 사용여부를 결정하기 때문에 Optimizer Mode는 반드시 CBO(cost based optimizer) Mode인 ALL_ROWS 또는 FIRST_ROWS로 지정되거나 , CHOOSE mode인 경우 해당 object는 ANALYZE 명령을 통하여 통계정보를 생성해 주어야 한다.
IOT(Index Organized Table)에는 rowid를 가지고 있지 않기 때문에 rowid range로 표현되는 Bitmap Index를 사용할 수 없다. 그러나 Oracle9i부터는 ALTER TABLE .. MOVE MAPPING TABLE과 같이 mapping table을 이용하여 Bitmap Index를 사용할 수 있다.
2.2.3 비트맵(Bitmap) 인덱스의 엑세스
비트맵 인덱스 사용시 실행계획 오퍼레이션
NOT,OR 이 포함된 쿼리에 실행계획
COL1 인덱스에서 '123'인 비트맵을 엑세스하고,COL2인덱스에서 'ABC'인 비트맵을 엑세스하여 감산연산을 수행한다(BITMAP MINUS)
COL2에서 NULL인 비트맵과 1의 결과를 다시 감산연산을 수행한다(BITMAP MINUS)
COL3 < 100 인 비트맵을 읽어 머지(BITMAP MERGE)를 수행하여 하나의 비트맵을 만든다.
2와 3에서 수행한 결과에 대해 OR연산(BITMAP OR)을 수행하여 조건을 만족하는 최종결과 비트맵을 만들고 ROWID로 변형하여 테이블을 엑세스 한다.
B-Tree 인덱스와 비트맵(Bitmap)인덱스의 비교
2.3 리버스 키 인덱스 (Reverse key index)
어떤 컬럼값의 각 바이트의 위치를 역전시키는 것을 말한다.
리버스키 인덱스에서는 정렬이 크게 달라지므로 클러스터링 팩터가 나빠진다는 것을 의미한다.
'='로만 액세스하는 경우에라면 크게 문제되것이 없다
2.4 함수기반 인덱스(FBI, Function-Based index)
2.3.1 함수기반 인덱스의 제약사항
비용기준 옵티마이져(CBO)에서만 사용가능한다.
함수기반 인덱스를 생성한 후 반드시 통계정보를 생성해야 한다.
사용자지정함수는 반드시 DETERMINISTIC으로 선언되어야 한다.
QUERY_REWRITE_ENABLE parameter 는 TRUE로 선언되어야 한다.
QUERY_REWRITE_INTEGRITY parameter는 TRUSTED로 선언되어야 한다.
다음의 사용자 권한을 가져야한다.
함수나 수식의 결과가 NULL인 경우는 이 인덱스를 통해 액세스할 수 없다.
사용자 지정함수를 사용한 경우에는 종속성 유지에 주의해야 한다.
옵티마이져가 사용불가 상태가 된 인덱스를 선택하면 SQL의 실행은 실패한다.
스칼라 서브쿼리로 표현된 컬럼은 함수기반 인덱스를 사용할 수 없다.
값이 상황에 따라 달라질 수 있는 SYSDATE, USER, ROWNUM 등의 가상컬럼이 포함되면 이 인덱스를 생성할 수 없다.
파티션을 적용한 경우에 파티션 키를 함수기반 인덱스에 사용할 수 없다.
숫자 컬럼을 문자 연산하거나 문자컬럼을 수치연산하는 수식의 경우에는 직접 기술하지 않았더라도 내부적으로 TO_CHAR, TO_NUMBER가 추가되어 처리된다.
함수기반 인덱스에서는 NLS파라메터를 현재 기준으로 적용하기 때문에 만약 세션 레벨에서 재정의를 한다면 잘못된 결과를 얻을 수도 있으므로 주의해야 한다.
WHERE 절에 기술된 컬럼이 표현된 것과 인덱스에 지정된 표현이 다르더라도 논리적으로 교환법칙이 성립하는 경우라면 같은 결과를 얻을 수 있다.
2.3.2 함수기반 인덱스의 활용
가.테이블 설계상의 문제를 해결
컬럼의 중간부분을 검색
create index from_loc_idx on orders (substr(ship_id,5,3));
create index repair_loc_idx on orders (substr(ship_id,3,2), ord_date);
조인 연결고리 컬럼이 대응하지 않는 경우의 해결
정상적인 데이터 모델링을 수행했다면 나타날 수 없겠지만 현실에서는 가끔 등장하는 형태. 상위테이블의 품목분류테이블(itemgrop)에서는 상세한 관리를 위해 컬럼을 대분류(class1), 중분류(class2), 소분류(class3)으로 나누었으나 하위 테이블(items)에서는 컬럼이 늘어날 것을 염려하여 그룹코드(group_cd)라는 하나의 컬럼으로 생성한 예이다. 다음과 같이 조인을 할 경우 한쪽 연결고리에 이상이 발생하여 조인에 나쁜 영향을 미칠 수 있다.
select ... from item_group x, items y
where x.class1 || x.class2 || x.class3 = y.group_cd
OR
select ...
from item_group x, items y
where x.class1 = substr(y.group_cd,1,2)
and x.class2 = substr(y.group_cd,3,2)
and x.class3 = substr(y.group_cd,5,3)
위와 같은 경우 다음과 같은 함수기반 인덱스로 해결할 수 있다.
create index group_cd_idx on item_group(x.class1 || x.class2 || x.class3 );
일자컬럼이 분할 된 경우의 해결
일자컬럼이 함부로 년,월,일로 분할 되어 관리되는 경우 년,월,일을 결하여 비교할 수 밖에 없으므로 조인 연결고리 컬럼이 대응하지 않는 경우과 같아진다.
create index sal_date_idx on sales (sal_yyyy || sal_mm || sal_dd);
데이터 타입이 상이한 조인 컬럼
create index deptno_idx on emp (to_number(deptno));
조인컬럼이 경우에 따라 달라지는 경우의 조인
select ... from sales s, departments d
where d.deptno = (case when sal_type = 1 then
sal_dept
else
agent_no
end)
and d.location = 'PUSAN';
create index deptno_idx on sales
(case when sal_type = 1 then sal_dept
else
agent_no
end);
부모테이블의 컬럼과 결합한 인덱스 생성
select ...
from movement x, movement_trans y
where x.mov_order = y.mov_order
and x.deptno = '12310'
and y.mov_date like '200512%'
위와 같은 sql이 있다면 x.deptno = '12310'이 처리 주관 조건이 된다면 move_date like '200512%'는 체크기능의 역할만 하게 된다.
이때 두개의 조건이 함께 처리 주관조건이 된다면 아주 양호한 수행속도를 얻을 수 있다면 다음과 같은 함수기반인덱스를 이용하여 해결 할 수 있다.
create or replace function get_deptno(v_mov_order in number)
return varchar2 DETERMINISTIC is
ret_val varchar2(5);
begin
select deptno into ret_val
from movement
where mov_order = v_mov_order;
return ret_val;
end get_deptno;
create index dept_date_idx on movement_trans(get_deptno(mov_order),mov_date);
나. 오류데이터의 검색문제를 해결
대소문자나 공백이 혼재된 컬럼의 검색
create index ename_upper_idx on employees (upper (ename));
== 불필요한 공백을 제거한 후 비교를 해야하는 경우
create index ename_trim_idx on employees(replace(ename,' '));
접두사를 채워서 검색
create index call_number_idx
on call_data (decode(substr(call_number,1,3),'018','','016')||call_number);
다. 가공처리 결과의 검색
복잡한 계산결과의 검색
create index order_amount_idx
on order_items(item_cd, (order_price-nvl(order_discount,0),0) * order_count));
select /*+ index_desc(x order_amount_idx) */ *
from order_items x
where item_cd = :b1
and rownum <= 100
create index sal_amount_idx on sales (last_day(sal_date), sal_amount);
create index term_idx on activities (expire_date - start_date)
create index source_length_idx on print_media(text_length(source_text));
라. 오브젝트 타입의 인덱스 검색
길이,폭,높이 세가지 컬럼을 이용하여 부피를 구하는 volume()메소드를 정의
create type cube AS object
(
length number,
width number,
heigth number,
member function volume return number DETERMINISTIC
);
create or replace type body cube as
member function volume return number is
begin
return (length * width * height);
end;
end;
cube유형(TYPE)으로된 CUBE_TAB 테이블을 생성하고, volumn() 메소드로된 함수기반 인덱스 생성
create table cube_tab of cube;
create index volume_idx on cube_tab x (x.volume());
인덱스를 경유하여 엑세스를 수행
select * from cube_tab x where x.volume() > 100
마. 베타적 관계의 인덱스 검색
배타적 관계의 유일성 보장
create unique index official_id_idx
on customers(case when cust_type =1 then resident_id else business_id end);
select * from customers
where (case when cust_type =1 then resident_id else business_id end) = :b1;
create unique index contract_idx on insurance (
case when then ins_type = 'A01' then customer_id else null end,
case when then ins_type = 'A01' then ins_type else null end
);
insert into contact_person
insuran_id, ..., customer_id, ins_type) values (122101, ..., 2101, 'A01');
1row created.
insert into contact_person
(insuran_id, ..., customer_id, ins_type) values (122102, ..., 2101, 'A01');
ERROR at line 1;
unique constraint (OE.CONTRACT_IDX) violated
배타적관계의 결합인덱스
create index order_deliver_idx1 on order_delivery(
order_dept, //고정된 선행컬럼
case when ord_type=1 then delivery_date else shipping_date end), // 중간 컬럼인덱스
item_type
)