SKIP SCAN
인덱스 리프 블록 간에는 양방향으로 포인터를 통해 연결돼 있으므로 인덱스 사용을 위한 기본적인 Range Scan 매커니즘은, 스캔을 시작할 블록을 찾기 위해 한 번만 브랜치 레벨을 통해 아래로 내려가야 한다.
Skip Scan 매커니즘에서는 브랜치 레벨을 따라 위아래로 이동해야 한다. 따라서 공식이 다를 뿐 아니라 버퍼를 PIN하는 전략 또한 달라 지며, 리프 블록을 스캔하는 동안 루트에서 현재 읽는 리프 블록까지의 경로 상에 있는 모든 블록을 PIN하는 것 같다. 곧바로 다시 액세스할 브랜치 블록인데 다른 프로세스가 해당 버퍼 블록을 Split 하면 안 되기 때문에 그렇게 하는 것이다.
인덱스 높이
create table t1 nologging
as
select
trunc(dbms_random.value(0,25)) n1, rpad('x',40) ind_pad, trunc(dbms_random.value(0,20)) n2, lpad(rownum,10,'0') small_vc, rpad('x',200) padding
fromall_obj
ectswhererownum <= 10000 ;
create index t1_i1 on t1(n1, ind_pad, n2)
nologging pctfree 91
-- 코드 설명* 처음 생성될 브랜치에 PCTFREE가 적용되지 않고, IND_PAD가 모든 로웨 같은 값을 가지므로
-- 전체 통계와 분산도에는 영향을 미치지 않으면서 오라클이 각 브랜치 블록에 적은 수의 로우만을 갖도록 하였음이로 인해
-- 인덱스 높이를 증가시켜 blevel 2로 만들었다.
인덱스에서의 PCTFREE
TABLE_NAME BLOCKS NUM_ROWS
-------------------- ---------- ----------
T1 371 10000
NUM_ROWS DISTINCT_KEYS BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY
---------- ------------- ---------- ----------- ----------------- ----------------------- -----------------------
10000 500 2 1111 9745 2 19
COLUMN_NAM NUM_NULLS NUM_DISTINCT DENSITY
---------- ---------- ------------ ----------
IND_PAD 0 1 1 N1 0 25 .04 N2 0 20 .05
select small_vc
fromt1
wheren1 = 2
and ind_pad = rpad('x',40)
and n2 = 3 ;
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------------------
| 0 |SELECTSTATEMENT | | 20 | 1160 | 25 |
| 1 | TABLE ACCESSBYINDEX ROWID| T1 | 20 | 1160 | 25 |
|* 2 | INDEX RANGE SCAN | T1_I1 | 20 | | 5 |
---------------------------------------------------------------------
select
small_vc
from
t1
where
n1 = 2
and ind_pad = rpad('x',40)
and n2 = 3
and small_vc = '0000000001'
- 위에 처럼 조건절을 하나 더 추가(and small_vc = '0000000001' 추가)할 경우를 보면, 옵티마이저가 현재 생성해 둔 인덱스를 경유해 테이블을 방문하기로 했다면 테이블을 액세스하기 전까지는 마지막에 추가된 이 조건을 체크할 수 없다.
- 이 조건은 테이블을 방문해야 할 데이터 비율에는 미치지 않으며, 최종적으로 턴하는 데이터 비율에만 영향을 미친다.
- 이 사례에서는 테이블에 대한 조건절 모두 인덱스 액세스 단계에서 체크가 가능하므로 유효 테이블 선택도 역시 0.04 * 1* 0.05 = 0.002이다.
인덱스와 클러스터링 팩터
인덱스 재생성
select
small_vc
from
t1
where
n1 = 2
and ind_pad = rpad('x',40)
and n2 between 1 and 3
;
----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
----------------------------------------------------------
| 0 | SELECT STATEMENT | | 82 | 4756 | 37 |
|* 1 | TABLE ACCESS FULL| T1 | 82 | 4756 | 37 |
----------------------------------------------------------
select
/*+ index(t1) */
small_vc
from
t1
where
n1 = 2
and ind_pad = rpad('x',40)
and n2 between 1 and 3
;
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 82 | 4756 | 93 |
| 1 | TABLE ACCESS BY INDEX ROWID| T1 | 82 | 4756 | 93 |
|* 2 | INDEX RANGE SCAN | T1_I1 | 82 | | 12 |
---------------------------------------------------------------------
유효 테이블 선택도 = 1 * 0.04 * 0.025263 = 0.0082105
Cost = 2 + ceiling (1,111 * 0.0082105 ) + ----10
Ceiling (9745 * 0.0082105 ) + ----81
= 12 (실행 계획 인덱스 라인) + 81 = 93
8i와 9i 실행계획의 변경
alter session set "_optimizer_skip_scan_enabled"=false;
select
/*+ index(t1) */
small_vc
from
t1
where
n1 between 1 and 3
and ind_pad = rpad('x',40)
and n2 = 2
;
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 82 | 4756 | 264 (0)| 00:00:04 |
| 1 | TABLE ACCESS BY INDEX ROWID| T1 | 82 | 4756 | 264 (0)| 00:00:04 |
|* 2 | INDEX RANGE SCAN | T1_I1 | 82 | | 184 (0)| 00:00:03 |
-------------------------------------------------------------------------------------
비용 = 2+ -belevel
Ceiling(0.1633333 * 11111) + -- 182
Ceiling(0.0081667 * 9745 ) + -- 80
= 184 + 80 = 264
확장된 실행계획 정보
alter session set "_optimizer_skip_scan_enabled"=false;
select
/*+ index(t1) */
small_vc
from
t1
where n2 = 2
order by n1
;
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 500 | 8500 | 1601 |
| 1 | TABLE ACCESS BY INDEX ROWID| T1 | 500 | 8500 | 1601 |
|* 2 | INDEX FULL SCAN | T1_I1 | 500 | | 1113 |
---------------------------------------------------------------------
Cost =
2+(1*1111) + (0.05 * 9745) =
2+1111 + 487.25 = --round() or ceil() ?
1113 + 488 = 1601 - ceil 적용
h5. 인덱스만 읽고 처리하는 쿼리
{code:sql}
select
/*+ index(t1) */
n2
from
t1
where n1 between 6 and 9
order by n1
;
----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
----------------------------------------------------------
| 0 | SELECT STATEMENT | | 2050 | 12300 | 230 |
|* 1 | INDEX RANGE SCAN| T1_I1 | 2050 | 12300 | 230 |
----------------------------------------------------------
\\
h5. 끝으로
* 내부적으로 round()와 ceil()을 사용할 때 오라클 버전 사이에 다양성이 존재 한다.
* 기본 공식과 실제 결과가 다르게 나타는 경우가 있다.
** Unique 인덱스 이거나, Unique 또는 Primary Key 제약을 지원하는 Nonunique (인덱스 : uniqueness 체크를 위해 Nonunique인덱스 사용) 인 경우 옵티마이저는 기본 공식을 사용한 후에 거기서 1을 뺀다. 그러나 Unique 또는 Primary Key 제약을 지원하는 Nonunique 인덱스이더라도 제약에 defferable 옵션을 사용할 때는 이런 조정이 일어나지 않는다. 그래서 주의가 필요한데, 만일에 대비하기 위해 모든 제약에 deferrable옵션을 주고 싶은 유혹이 들 수 있고, 이 때문에 실행계획이 변하는 약간의 부작용이 새길 수 있다.
** 인덱스 blevel이 1로 설정(즉, 루트 블록에서 리프 블록으로 직접 연결)된 경우, 모든 인덱스 컬럼이 '=' 조건으로 사용된다면 옵티마이저는 blevel을 사실상 무시한다. 그런데 (테이블에 있는 한 로우가 업데이트되는 순간 갑자기) 루트 블록 분할(split)dl 발생해서 인덱스 비용이 2만큼 증가하면 액세스 경로가 바뀔수 있기 때문에 관심을 갖고 살펴 볼만한 사례이다
\\
{info:title=작은 테이블에 생성한 인덱스}
작은 테이블에 생성한 인덱스와 통계 정보를 다루는 전략은 매우 중요한 문제이다. 일부 작은 인덱스들은 주기적인 재성성이 필요한 것처럼 보이게 할 수 있기 때문이다. Blevel이 1에서 2로 전환되기 직전 상태에 놓인 인덱스에 통계 정보를 계속해서 수집하다 보면 하나의 추가적인 로우 삽입으로 인해 어느 순간 blevel이 1에서 2로 바뀔수 있다. 데이터 사이즈 또는 인덱스 블록 수에는 그다지 큰 변화가 없지만 비용 계산 결과가 갑자기 높아지는 원인이 된다.
이런 비정상적인 상태에 놓인 인덱스들이 있을 때 선택할 수 있는 첫 번째 옵션은, 통게 정보 수집을 멈추는 것이다. 두 번째, 통계 정보를 수집한 후에 수작업으로 blevel을 설정해 주는 것이다. 세번째는, 통계 정보를 수집하기 전에 매번 인덱스를 재생성하는 것이다.
{info}
\\
* B-tree 인덱스를 사용할 때 비용은 다음 세 가지 구성요소로 이루어 진다.
** 인덱스 깊이 : blevel에 기초해서 계산
** 스캔해야 할 인덱스 리프 블록수 : leaf_blocks에 기초해서 계산
** 테이블 블록 방문 횟수 : clustering_factor에 기초해서 계산
- 강좌 URL : http://www.gurubee.net/lecture/3977
- 구루비 강좌는 개인의 학습용으로만 사용 할 수 있으며, 다른 웹 페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^
- 구루비 강좌는 서비스 제공을 위한 목적이나, 학원 홍보, 수익을 얻기 위한 용도로 사용 할 수 없습니다.