study:oracle:datadb:1week_2
문서의 이전 판입니다!
목차
* 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 인덱스
- 생성 명령어 : CREATE INDEX 인덱스명 ON 테이블명(컬럼명)
- 나무구조(Tree)처럼 가지에 해당하는 브랜치 블럭과 잎사귀에 해당하는 리프 블럭이 있다.
2.1.1 B-tree 인덱스의 구조
:——————————————————————————————————
- Unique Scan일 경우
- range Scan일 경우
- 인덱스는 구성컬럼과 ROWID로 정렬되어 있다.
- ROWID에는 로우가 저장된 블록의 주소, 로우의 슬롯번호가 기록되어 있다.
- 처리할 범위의 첫번째 인덱스로우를 브랜체블록을 통해 리프블록을 액세스한 다음 거기의 ROWID로 블록을 찾는다.
- SGA부터 찾아보고 없으면 디스크에 액세스해서 복사본을 SGA에 복사한다.
- 새롭게 액세스한 데이터블록정보를 PGA로 가져와서 해당 SQL의 처리버퍼에 넣는다 (일명 원 버퍼).
- 인덱스블록을 액세스한 내용을 PGA버퍼에서 찾아 다른 조건까지 검증하여 성공한 로우만 운반단위로 보낸다 만약 클러스터링 팩터가 좋다면 여러건의 인덱스 스캔을 하나의 버퍼에서 처리할 수 있다.
- PGA버퍼에서 찾을 수 없다면 다시 다른 블록을 SGA에 액세스하여 새로운 블럭을 담고 위의 처리를 반복수행한다.
- 리프블록에서 액세스한 로우가 처리범위를 넘으면 처리를 종료한다.
ROWID의 구성
2.1.2 B-tree 인덱스의 조작(Operation)
가.인덱스 생성(Creation)
- 테이블을 액새스하여 정렬을 수행.
- 정렬된 결과를 인덱스 세그먼트의 리프 블록에 기록하기 시작.
- 리프블록이 PCTFREE에 도달하여 새로운 블록을 요구하게 되면 브랜치블록을 생성.
- 리프블록이 증가함에 따라 브랜치블록도 채워져 가다 블랜치블록이 PCTFREE에 도달하면 새로운 리프블록과 새로운 브랜치블록을 생성할때 루트블록이 생성된다.
- 새로운 브랜치블록이 만들어질 때마다 루트블록에 브랜치블록의 DBA정보를 기록한다.
- 위와 같은 방법으로 인덱스가 저장되기 때문에 인덱스 블럭에 많은 로우를 담게 될 경우 리프 블럭 감소, 브랜치 블럭 증가 둔화(블럭의 깊이 감소)
인덱스 블록에 보다 많은 로우를 담기 위해 취할 수 있는방법
- 최대한 인덱스 컬럼의 수를 줄인다.
- 큰 블럭 사이즈(DB_BLOCK_SIZE)를 지정. (데이터베이스 전체, 혹은 테이블스페이스에 대한 정의이다.)
- PCTFREE를 최소로 정의. (0으로 해도 무관함)
- 압축을 활용하는 방법
CREATE INDEX ord_customer_idx ON orders (customer_id, sales_id) COMPRESS 1;
:——————————————————————————————————
나.인덱스 블럭의 분할(SPILT)
- 인덱스 로우는 정렬이 되어 저장 되어야 하는 이유 때문에 이미 생성된 구조에 새로운 로우가 삽입되면 기존의 위치에 파고 들어가는 문제 발생
- 마지막 값이 삽입되는 경우 : 그림처럼 PCTFREE에 도달한 블럭일 경우엔 새로운 블럭에 등록된다.
- 중간 값이 삽입되는 경우 : PCTFREE가 초과하게 되므로 분할이 발생하는데 어차피 2개의 블럭으로 수정되므로 2/3만 채우도록 하면서 양쪽을 모두 재편하게 됨(또 다른 중간 값이 들어와 계속 분할 되는 것을 방지하기 위함).
- 하지만 분할이 일어난 블럭에 새로운 값이 들어오지 않으면 저장공간 낭비를 초래하게 되고 이를 해결하기 위해선 인덱스를 재생성하는 방법뿐이다.
:——————————————————————————————————
다.데이터의 삭제 및 갱신
- 데이터를 삭제했을 경우 테이블의 로우는 제거되지만 인덱스의 로우는 삭제되었다는 표시(flag)만 추가된다.(저장공간낭비와 스캔시 액세스 블럭 증가)
- 한 리프 블럭이 모두 삭제 되었을 경우 브랜치 블럭에 해당 리프 블럭을 가리키는 로우도 삭제 표시가 된다.
- 갱신할 경우 삭제와 삽입이 발생.
- 이러한 이유로 수정이 빈번하지 않는 컬럼을 인덱스로 사용권장.
- 데이터처리(DML)가 많이 수행되는 테이블은 정기적으로 재생성을 할 필요가 있다.
:——————————————————————————————————
라.인덱스를 경유한 검색
- lmc는 브랜치 블럭의 첫 번째 로우의 값보다 적은 값을 갖는 하위의 블럭의 주소정보(dba:data block address)를 말한다.
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) 인덱스
- 컴퓨터에서 사용하는 최소의 단위인 비트를 사용하여 컬럼 값을 저장하고 이를 이용하여 rowid를 자동으로 생성하는 인덱스 방법으로 비트를 직접 관리하므로 저장공간이 감소하고 비트연산이 용이 하다는 장점이 있다.
- 생성 명령어 : CREATE BITMAP INDEX … ON …
2.2.1 비트맵(Bitmap) 인덱스의 탄생 배경
- B-TREE인덱스가 가지는 문제점을 독립적으로 구성된 각각의 비트맵 인덱스가 조합되어 해결할 수 있다.
2.2.2 비트맵(Bitmap) 인덱스의 구조와 특성
구조
특성
- 비트를 저장할 때는 '선분 형태(Start rowid ~ End rowid)'로 저장을 하므로 일종의 압축 개념이 되며, 키 압축(Key Compression)이 적용되어 저장 공간이 매우 절약된다.
- 비트맵 연산을 통해 처리하므로 OR 연산이 가능하다.
- NULL 값도 BIT를 구성하고 있어 NULL 및 NOT 조건검색이 가능하다.
- 수정이 빈번하게 발생하는 컬럼은 인덱스의 크기가 크게 증가하고 블록레벨 잠금(Block Level Locking)으로 인해 많은 부하가 유발될 수 있다.
- 데이터 웨어하우스 업무에 주로 활용된다.
생성절차
- 인덱스를 생성하고자 하는 컬럼의 값들을 찾기 위해 테이블 스캔을 한 후
- 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로 변형하여 테이블을 엑세스 한다.
- 제한사항
- 파티션테이블에서 Global Index에는 비트맵인덱스를 만들 수 없다.
- 비트맵인덱스는 RBO(Rule Base optimizxer) mode에서는 사용될 수 없다.
B-Tree 인덱스와 비트맵(Bitmap)인덱스의 비교
2.3 리버스 키 인덱스 (Reverse key index)
- 어떤 컬럼값의 각 바이트의 위치를 역전시키는 것을 말한다.
- 리버스키 인덱스에서는 정렬이 크게 달라지므로 클러스터링 팩터가 나빠진다는 것을 의미한다.
- '='로만 액세스하는 경우에라면 크게 문제되것이 없다
2.4 함수기반 인덱스(FBI, Function-Based index)
2.3.1 함수기반 인덱스의 제약사항
- 비용기준 옵티마이져(CBO)에서만 사용가능한다.
- 함수기반 인덱스를 생성한 후 반드시 통계정보를 생성해야 한다.
- 사용자지정함수는 반드시 DETERMINISTIC으로 선언되어야 한다.
- QUERY_REWRITE_ENABLE parameter 는 TRUE로 선언되어야 한다.
- SQL>alter session set QUERY_REWRITE_ENABLE = TRUE;
- QUERY_REWRITE_INTEGRITY parameter는 TRUSTED로 선언되어야 한다.
- SQL>alter session set QUERY_REWRITE_INTEGRITY=TRUSTED;
- 다음의 사용자 권한을 가져야한다.
- 인덱스 생성권한 : INDEX CREATE / ANY INDEX CREATE
- 쿼리 재생성권한 : QUERY REWRITE / GLOBAL QUERY REWRITE
- 함수나 수식의 결과가 NULL인 경우는 이 인덱스를 통해 액세스할 수 없다.
- 사용자 지정함수를 사용한 경우에는 종속성 유지에 주의해야 한다.
- 인덱스 정의에 사용된 사용자 함수가 재정의되었을 때 사용불가 상태가 된다.
- 인덱스 소유자의 실행 권한이 취소 되면 사용불가 상태가 된다.
- 옵티마이져가 사용불가 상태가 된 인덱스를 선택하면 SQL의 실행은 실패한다.
- 인덱스를 가용상태로 만들거나 재구축해야 한다.
- 인덱스를 미사용상태로 만든다 이때 SKIP_UNUSABLE_INDEXES파라미터가 TRUE로 선언되어 있어야 한다.
- 스칼라 서브쿼리로 표현된 컬럼은 함수기반 인덱스를 사용할 수 없다.
- 값이 상황에 따라 달라질 수 있는 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,' '));
접두사를 채워서 검색
- 전화번호 앞번호가 018이면 그냥 두고 아니면 016을 붙여서 비교할 때
create index call_number_idx on call_data (decode(substr(call_number,1,3),'018','','016')||call_number);
다. 가공처리 결과의 검색
복잡한 계산결과의 검색
- 상품별로 데이터량이 많을 때 주문단가에서 할인금액을 뺀 실제 적용단가에 주문수량을 곱하여 금액별로 100개의 상품을 조회할때 복잡한 계산식을 함수기반인덱스로 만들어 결과값을 논리컬럼으로 저장해두면 힌트에서 인덱스를 사용할 수 있다.
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
- 말일, 단가, 율의 검색 : 해당월에 가장 높은 sal_amount가 발생한 건을 찾는 경우
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));
라. 오브젝트 타입의 인덱스 검색
- 육면체(cube)를 오브젝트타입으로 생성하오 이것을 이용하여 부피를 구하는 예
길이,폭,높이 세가지 컬럼을 이용하여 부피를 구하는 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
마. 베타적 관계의 인덱스 검색
- 여기서 말하는 배타적(Exclusive)관계에 있는 컬럼이란 데이터 모델링 상에서 서로 다른 서브타입(Subtype)에 있는 컬럼들 중에서 동일한 속성으로 통합할 수 있는 컬럼들의 관계를 말한다.
배타적 관계의 유일성 보장
- 어떤 배타적 관계에 있는 속성들을 관찰하면 동일한 속성(subtype)으로 볼 수 있으며 이들을 결합했을 경우 반드시 존재해야한다거나 유일해야 한다는 원칙이 있을 수 있다. 이런경우 함수기반 인덱스를 생성하여 처리의 단순화 및 시스템을 통한 제약조건의 검증까지 보장받을 수 있다.
- 배타적 관계에 있는 주민등록번호와 사업자등록번호를 결합하여 유일성을 체크할 수 있을 뿐 아니라 검색의 단순화를 얻을수 있는 예
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;
- 특정한 범위의 데이터에 대해서만 유일성을 체크할도록 할 수 있는 사례.
- 예를들어 'A01'의 보험상품에서는 고객이 한 번만 가입할 수 있다면 다음과 같은 방법으로 유일성을 체크할 수 있다.
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 );
- 이 인덱스는 INS_TYPE ='A01'인 경우가 아니면 모두 NULL 값이 되므로 유일성을 체크하지 않는다.
- 다음과 같은 입력이 발생하면 에러가 나타단다.
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 )
study/oracle/datadb/1week_2.1273133844.txt.gz · 마지막으로 수정됨: 저자 ahmax












