Index Range Scan 으로 데이터 추출하는 실행계획
일반적으로 Oracle 은 Leaf Block 에 속한 Key 값을 한번에 읽어 들인다.
그럼 Logical read = Leaf Block !? 동일하다 확인해 보자~
{CODE:SQL}
SQL> create table t1(c1 int, c2 int);
SQL> create index t1_c1 on t1(c1);
SQL> insert into t1
2 select level , level from dual
3 connect by level <= 10000;
10000 rows created.
SQL> exec dbms_stats.gather_table_stats('JIN','T1');
PL/SQL procedure successfully completed.
SQL> explain plan for
2 select /*+ index(t1) */
3 c1
4 from t1
5 where c1 >= 5
6 ;
Explained.
Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
0 | SELECT STATEMENT | 9997 | 39988 | 20 (0) | 00:00:01 | |
| INDEX RANGE SCAN | T1_N1 | 9997 | 39988 | 20 (0) | 00:00:01 |
select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 9997 | 20 (0) | 9996 | 00:00:00.02 | 686 |
여기서 의미하는 Logical Read 는 어디에서 나왔을까!?
{CODE:SQL}
SQL> @dic t1_n1
OWNER : JIN
INDEX_NAME : T1_N1
INDEX_TYPE : NORMAL
TABLE_OWNER : JIN
TABLE_NAME : T1
TABLE_TYPE : TABLE
UNIQUENESS : NONUNIQUE
COMPRESSION : DISABLED
PREFIX_LENGTH :
TABLESPACE_NAME : USERS
INI_TRANS : 2
MAX_TRANS : 255
INITIAL_EXTENT : 65536
NEXT_EXTENT :
MIN_EXTENTS : 1
MAX_EXTENTS : 2147483645
PCT_INCREASE :
PCT_THRESHOLD :
INCLUDE_COLUMN :
FREELISTS :
FREELIST_GROUPS :
PCT_FREE : 10
LOGGING : YES
BLEVEL : 1
LEAF_BLOCKS : 19
DISTINCT_KEYS : 10000
AVG_LEAF_BLOCKS_PER_KEY : 1
AVG_DATA_BLOCKS_PER_KEY : 1
CLUSTERING_FACTOR : 18
STATUS : VALID
NUM_ROWS : 10000
SAMPLE_SIZE : 10000
LAST_ANALYZED : 22-MAR-11
DEGREE : 1
INSTANCES : 1
PARTITIONED : NO
TEMPORARY : N
GENERATED : N
SECONDARY : N
BUFFER_POOL : DEFAULT
USER_STATS : NO
DURATION :
PCT_DIRECT_ACCESS :
ITYP_OWNER :
ITYP_NAME :
PARAMETERS :
GLOBAL_STATS : YES
DOMIDX_STATUS :
DOMIDX_OPSTATUS :
FUNCIDX_STATUS :
JOIN_INDEX : NO
IOT_REDUNDANT_PKEY_ELIM : NO
DROPPED : NO
select /*+ gather_plan_statistics index(t1) / count() from t1 where c1 >= 5
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.01 | 20 | ||
| INDEX RANGE SCAN | T1_N1 | 1 | 9997 | 20 (0) | 9996 | 00:00:00.02 | 20 |
{CODE}
{NOTE}
> Rootblock + Leafblock = 20
즉 한번의 Fetch 만 LeafBlock 을 Range SCAN 하는 경우 LeafBlock 의 수 만큼 Read가 발생한다.
이말을 반대로 해색하면 FetchCall 단위가 바뀔 때마다 매번 Block 을 로 일거야 한다.
SQL Plus 의 Fetch array size 는 15 이다. 9996 의 row를 읽을라면
9996/15 = 666 (추가방문) + 20(leaf block) 수. 686 이 Logical Read 의 수가 된다.
{NOTE}
{CODE:SQL}
Fetch Array Size
select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
set arraysize 1
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 9997 | 20 (0) | 9996 | 00:00:00.02 | 5009 |
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 9997 | 20 (0) | 9996 | 00:00:00.02 | 5009 |
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 9997 | 20 (0) | 9996 | 00:00:00.02 | 686 |
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 9997 | 20 (0) | 9996 | 00:00:00.02 | 119 |
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 9997 | 20 (0) | 9996 | 00:00:00.02 | 30 |
{CODE}
Fetch Array Size 값이 Index Range Scan 의 Leaf Block 방문 제한 회수를 제한한다.
{CODE:SQL}
statistics
NAME VALUE1 VALUE2 DIFF
time model
STAT_NAME VALUE1 VALUE2 DIFF
wait event
EVENT D_WAITS D_TIMEOUTS d_time_waited(s)
{CODE}
즉 Array Size 를 변경한다고 해도 전송해야 할 총량은 변하지 않지만, Fetch Call횟수는 줄일 수 있다.
그럼 적당한 운반단위는 얼마가 될까? 적당한게 좋다.
(네트워크를 통해 전송해야 할 데이터 양에 따라 Array Size 조절 할 필요가 있음)
Oracle 은 조건에 해당되는 Key 값들이 삭제된 것을미리 알고 불필요한 Range Scan 을 피할 수 있는가?
일반적인 상태
{CODE:SQL}
SQL> create table t1(c1 int, c2 int);
SQL> create index t1_n1 on t1(c1);
SQL> insert into t1
2 select level, level
3 from dual
4 connect by level <= 10000
5 ;
10000 rows created.
SQL> exec dbms_stats.gather_table_stats('JIN','T1');
SQL> select /*+ gather_plan_statistics index(t1) */
2 c1
3 from t1
4 where c1 >= 5
5 ;
select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
Plan hash value: 1088104427
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 9996 | 21 (0) | 9996 | 00:00:00.02 | 686 |
삭제 후 Leaf block 탐색 여부 확인.
{CODE:SQL}
SQL> delete from t1
2 where c1 >= 1
3 ;
10000 rows deleted.
SQL> exec dbms_stats.gather_table_stats('JIN','T1');
PL/SQL procedure successfully completed.
SQL> select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
2 ;
no rows selected
select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 1 | 1 (0) | 0 | 00:00:00.01 | 20 |
SQL> exec dbms_stats.gather_index_stats('JIN','T1_N1');
SQL> exec dbms_stats.gather_index_stats('JIN','T1_N1');
SQL> select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
2 ;
no rows selected
select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 1 | 1 (0) | 0 | 00:00:00.01 | 20 |
SQL> select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
2 ;
no rows selected
select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 1 | 1 (0) | 0 | 00:00:00.01 | 20 |
SQL> select INDEX_NAME,LEAF_BLOCKS from user_indexes;
INDEX_NAME LEAF_BLOCKS
{CODE}
{CODE:SQL}
SQL> alter index t1_n1 coalesce;
Index altered.
SQL> SQL> select /*+ gather_plan_statistics index(t1) */
2 c1
3 from t1
4 where c1 >= 5
5 ;
select /*+ gather_plan_statistics index(t1) */ c1 from t1 where c1 >= 5
Plan hash value: 1088104427
Id | Operation | Name | Starts | E-Rows | Cost (%CPU) | A-Rows | A-Time | Buffers |
| INDEX RANGE SCAN | T1_N1 | 1 | 1 | 1 (0) | 0 | 00:00:00.01 | 2 |
{CODE}
삭제 전, 후 상태 비교
{CODE:SQL}
OWNER : JIN : JIN
INDEX_NAME : T1_N1 : T1_N1
INDEX_TYPE : NORMAL : NORMAL
TABLE_OWNER : JIN : JIN
TABLE_NAME : T1 : T1
TABLE_TYPE : TABLE : TABLE
UNIQUENESS : NONUNIQUE : NONUNIQUE
COMPRESSION : DISABLED : DISABLED
PREFIX_LENGTH : :
TABLESPACE_NAME : USERS : USERS
INI_TRANS : 2 : 2
MAX_TRANS : 255 : 255
INITIAL_EXTENT : 65536 : 65536
NEXT_EXTENT : :
MIN_EXTENTS : 1 : 1
MAX_EXTENTS : 2147483645 : 2147483645
PCT_INCREASE : :
PCT_THRESHOLD : :
INCLUDE_COLUMN : :
FREELISTS : :
FREELIST_GROUPS : :
PCT_FREE : 10 : 10
LOGGING : YES : YES
BLEVEL : 1 : 1
LEAF_BLOCKS : 0 : 0
DISTINCT_KEYS : 0 : 0
AVG_LEAF_BLOCKS_PER_KEY : 0 : 0
AVG_DATA_BLOCKS_PER_KEY : 0 : 0
CLUSTERING_FACTOR : 0 : 0
STATUS : VALID : VALID
NUM_ROWS : 0 : 0
SAMPLE_SIZE : 0 : 0
LAST_ANALYZED : 23-MAR-11 : 23-MAR-11
DEGREE : 1 : 1
INSTANCES : 1 : 1
PARTITIONED : NO : NO
TEMPORARY : N : N
GENERATED : N : N
SECONDARY : N : N
BUFFER_POOL : DEFAULT : DEFAULT
USER_STATS : NO : NO
DURATION : :
PCT_DIRECT_ACCESS : :
ITYP_OWNER : :
ITYP_NAME : :
PARAMETERS : :
GLOBAL_STATS : YES : YES
DOMIDX_STATUS : :
DOMIDX_OPSTATUS : :
FUNCIDX_STATUS : :
JOIN_INDEX : NO : NO
IOT_REDUNDANT_PKEY_ELIM : NO : NO
DROPPED : NO : NO
{CODE}
문제는 Index Range Scan 과 Nested Loop Join 을 함꼐 사용하면 더 심각해진다.
{CODE:SQL}
SQL> explain plan for
2 select /*+ leading(t1) use_nl(t1 t2) index (t2) */ *
3 from t1,t2
4 where t1.c1=t2.c1;
Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time |
0 | SELECT STATEMENT | 10000 | 429K | 20031 (1) | 00:01:41 | |
1 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 22 | 2 (0) | 00:00:01 |
2 | NESTED LOOPS | 10000 | 429K | 20031 (1) | 00:01:41 | |
3 | TABLE ACCESS FULL | T1 | 10000 | 214K | 12 (0) | 00:00:01 |
| INDEX RANGE SCAN | T2_N1 | 1 | 1 (0) | 00:00:01 |
{CODE}
넓은 범위를 읽는것이 비효율적인지 알수있다
생성구문
{CODE:SQL}
SQL> drop table t1 purge;
SQL> drop table t2 purge;
SQL> create table t1(c1 char(10), c2 char(10));
SQL> create table t2(c1 char(10), c2 char(10));
SQL> insert into t1
2 select level, 'x'
3 from dual
4 connect by level <= 10000
5 ;
10000 rows created.
SQL> insert into t2
2 select level, 'x'
3 from dual
4 connect by level <= 10000
5 ;
10000 rows created.
create index t1_n1 on t1(c1);
create index t2_n1 on t2(c1);
10000 rows created.
SQL>exec dbms_stats.gather_table_stats('JIN', 't1');
SQL>exec dbms_stats.gather_table_stats('JIN', 't2');
{CODE}
Oracle Version Oracle 10.2.0.4
{CODE}
********************************************************************************
count = number of times OCI procedure was executed
cpu = cpu time in seconds executing
elapsed = elapsed time in seconds executing
disk = number of physical reads of buffers from disk
query = number of buffers gotten for consistent read
current = number of buffers gotten in current mode (usually for update)
rows = number of rows processed by the fetch or execute call
********************************************************************************
select /*+ leading(t1) use_nl(t1 t2) index (t2) */ *
from t1,t2
where t1.c1=t2.c1
call count cpu elapsed disk query current rows
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 59
Rows Row Source Operation
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
select /*+ leading(t1) use_nl(t1 t2) index (t2) */ *
from t1,t2
where t1.c1=t2.c1
call count cpu elapsed disk query current rows
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 53
Number of plan statistics captured: 1
Rows (1st) Rows (avg) Rows (max) Row Source Operation
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
10.2.0.3 부터 가능
index 선택 모호할 경우 직접 index_range_scan 으로 hint 줄 수 있다..