2024/02/07
상도형님꺼
※ hwm(High-Water Mart) 이동하는 과정에서 HW lock 경합이 발생할 수 있다.
이 경우 발생하는 대기 이벤트 ---> enq: HW - contention ---> system lock
★ Transaction 처리 순서
undo segment 할당
undo segment header 에 transaction table slot 을 생성
Transaction 의 대상이 되는 블록을 데이터 버퍼 캐시의 실행계획을 통해서 찾는다.
변경할 블록들의 변경 정보를 PGA 영역에서 change vector 를 생성한다.
- undo segment header 정보(change vector #1)
- undo block(change vector #2)
- transaction 의 대상이 되는 블록(change vector #3)
PGA 영역 안에 change vector 정보를 redo entry 라는 이름으로 redo log buffer 로 복사한다.
redo copy latch, redo allocation latch, redo writing latch 를 획득해야 한다.
이 과정에서 latch 경합이 발생하면
- latch : redo copy
- latch : redo allocation
- latch : redo writing
- log buffer space wait event 가 발생한다.
- log file switch completion wait event 가 발생할 수 있다.
log file switch completion wait event : 현재 리두 로그 파일이 꽉차서 로그 스위치가 발생하는 경우, LGWR
은 이 작업이 끝날 때까지 대기해야 한다.
이전 이미지(값)에 대한 정보를 undo block 에 기록하고 데이터 블록을 변경한다.
변경된 block 은 dirty 상태가 된다. ---> 메모리에 있는 내용과 디스크에 있는 내용이 다른 상태
변경된 block header 에 CR(Consistent Read) 블록으로 작성해 놓는다.
변경하는 행(row)에 대해서 lock 을 생성한다.
다른 세션에서 이 행(row)에 대해서 lock 을 걸고 있으면 기다려야 한다.
이 때 발생한 대기 이벤트는 enq: TX - row lock contention
★ commit
★ Latch
★ lock
<< hr session >>
HR@ora11g> drop table hr.emp purge;
drop table hr.emp purge
*
ERROR at line 1:
ORA-00942: table or view does not exist
HR@ora11g> create table hr.emp as select * from hr.employees;
Table created.
HR@ora11g> update hr.emp ---> 테이블(table)에 대해서 lock
set salary = salary * 1.1
where employee_id = 100; 2 3 ---> 행(row)에 대해서 lock
1 row updated.
<< sys session >>
SYS@ora11g> drop table hr.emp purge;
drop table hr.emp purge
*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
SYS@ora11g> select sid from v$session where username = 'HR';
SID
----------
26
SYS@ora11g> SELECT * FROM V$lock where sid = 26;
ADDR KADDR SID TY ID1 ID2 LMODE REQUEST CTIME BLOCK
-------- -------- ---------- -- ---------- ---------- ---------- ---------- ---------- ----------
4956B8A8 4956B8D4 26 TO 79605 1 3 0 449 0
4956BC1C 4956BC48 26 AE 100 0 4 0 477 0
F6FBC784 F6FBC7B4 26 TM 88675 0 3 0 365 0
48F53F88 48F53FC8 26 TX 983044 839 6 0 365 0
TYPE : TM
ID1 : OBJECT ID
select * from dba_objects where object_id = 88675;
select * from v$session where username = 'HR';
select sid from v$session where username = 'HR';
SELECT * FROM V$lock where sid = 26;
SELECT * FROM v$lock where sid = 188; ---> 다른 HR 세션을 열어서 동일한 UPDATE 문 실행
SELECT * FROM dba_objects where object_id = 88675;
select * from v$lock where sid in (26,188);
TYPE : TX
ID1 : undo segment 번호 + transaction slot 번호
ID2 : transaction slot sequence
LMODE > 0 이면 LOCK 을 보유 중인 세션
REQUEST > 0 이면 LOCK 을 요청 중인 세션
CTIME : 현재 LOCK 모드가 허용된 이후의 시간(second), 즉 lock 을 보유하거나 요청한 이후부터의 시간(초)
BLOCK : 현재 LOCK 이 다른 LOCK 을 블로킹하고 있는지 여부
1 : 다른 LOCK 을 블로킹 중.
0 : 다른 LOCK 을 블로킹하지 않음.
LMODE
Row Share(RS), Sub Share(SS), 2
LOCK 된 테이블에 대한 동시 액세스를 허용하지만 세션이 배타적(exclusive) 액세스를 위해 전체 테이블 LOCK 하는 것은
금지한다.
LOCK TABLE hr.emp IN ROW SHARE MODE;
Row eXclusive(RX), Sub eXclusive(SX), 3 : Row Share 와 동일하지만 shared 모드에서도 lock 을 금지한다.
Row eXclusive(RX) lock 은 데이터 갱신, 입력, 삭제시 자동으로 획득한다.
LOCK TABLE hr.emp IN ROW EXCLUSIVE MODE; ---> 수동으로
Share(S), 4 : 동시 query 는 허용하지만 lock 된 테이블에 대한 변경은 금지한다.
테이블에서 인덱스를 생성하려면 share lock 이 필요하며 자동으로 요청한다.
unique, primary key 충돌시 발생
LOCK TABLE hr.emp IN SHARE MODE; ---> 수동으로
Share Row eXclusive(SRX), Share Sub eXclusive(SSX) 5
전체 테이블을 query 하는데 사용되며 다른 유저가 테이블 행을 query 하는 것은 허용하지만 해당 테이블을 share mode
에서 lock 하거나 행(row)을 갱신하는 것은 금지한다.
row cache lock(sequence nocache 속성에서 nextval 을 호출할 때마다 dictionary 변경)
LOCK TABLE hr.emp IN SHARE ROW EXCLUSIVE MODE;
eXclusive(X), 6 : LOCK 된 테이블에서의 query 는 허용하지만 해당 테이블에서의 다른 작업은 금지한다.
즉 테이블에 DDL 작업 시에 exclusive lock 이 필요하다.
LOCK TABLE hr.emp IN ROW EXCLUSIVE MODE;
★ Redo 기능
★ LGWR에 의한 Redo 기록
★ Log Force at Commit
★ Write Ahead Log
8i : 버전 까지는 redo allocation latch 시스템에 1개여서 오직 하나의 프로세스만이 redo buffer 에서 메모리 할당할 수
있었다.
9i : shared redo strands : redo buffer 의 영역을 일정 개수로 분할해서 사용. 분할된 영역을 strand 라고 한다.
_log_parallelism 파라미터의 값을 이용해서 redo strand 의 개수를 지정.(cpu수/8) 권장사항
각 strand 마다 redo allocation latch 사용
10g : - shared redo strands 개수를 오라클이 동적으로 관리한다. _log_parallelism_dynamic 히든 파라미터를 TRUE로
설정.
- private redo strands 기능을 사용함으로써 리두데이터를 pga 영역에서 change vector 를 생성하는것이 아닌
shared pool 에 private strands 영역에 저장하며 이 영역에 저장된 로그 데이터는 redo buffer 를 거치지 않고
redo log file 에 저장함으로써 latch 경합을 최소화한다. _log_private_parallelism 히든 파라미터의 값을
true 로 설정하면 private redo strands 기능이 활성화된다.
10gR2 : private redo strands 를 위한 공간은 LOG_BUFFER 내에 생성되며 _log_pivate_mul 에 지정된 비율만큼(5퍼센트)
을 private redo strands 공간으로 사용. zero copy redo 라고도 한다.
SQL> SELECT a.ksppinm Parameter, b.ksppstvl Value
FROM x$ksppi a, x$ksppcv b
WHERE a.indx = b.indx
AND a.ksppinm in ('_log_parallelism_dynamic','_log_private_mul');
PARAMETER VALUE
-------------------------------------------------- ----------
_log_parallelism_dynamic TRUE
_log_private_mul 5
redo log buffer latch
============================================================================================================
create table hr.redo_table(id number, name char(100));
insert into hr.redo_table(id,name)
select object_id, object_name
from dba_objects;
select t.used_ublk, t.used_urec
from v$transaction t, v$session s
where s.sid = (select sid from v$mystat where rownum = 1)
and s.taddr = t.addr;
select n.name, sum(s.value)
from v$sesstat s, v$statname n
where n.name in ('redo entries','redo size','redo synch writes','redo writes','redo blocks written',
'redo log space requests','redo log space wait time')
and s.statistic# = n.statistic#
and s. sid = (select sid from v$mystat where rownum = 1)
group by n.name;
select blocks, bytes/1024/1024 mb
from dba_segments
where segment_name = 'REDO_TABLE';
SYS@ora11g> rollback;
Rollback complete.
SYS@ora11g> select n.name, sum(s.value)
from v$sesstat s, v$statname n
where n.name in ('redo entries','redo size','redo synch writes','redo writes','redo blocks written',
'redo log space requests','redo log space wait time')
and s.statistic# = n.statistic#
and s. sid = (select sid from v$mystat where rownum = 1)
group by n.name; 2 3 4 5 6 7
NAME SUM(S.VALUE)
-------------------------------------------------- ------------
redo entries 189
redo size 45004
redo log space requests 0
redo log space wait time 0
redo synch writes 2
redo blocks written 0
redo writes 0
SYS@ora11g> truncate table hr.redo_table;
Table truncated.
SYS@ora11g> select blocks, bytes/1024/1024 mb
from dba_segments
where segment_name = 'REDO_TABLE'; 2 3
BLOCKS MB
---------- ----------
8 .0625
<< NEW SESSION >>
SYS@ora11g> select n.name, sum(s.value)
from v$sesstat s, v$statname n
where n.name in ('redo entries','redo size','redo synch writes','redo writes','redo blocks written',
'redo log space requests','redo log space wait time')
and s.statistic# = n.statistic#
and s. sid = (select sid from v$mystat where rownum = 1)
group by n.name; 2 3 4 5 6 7
NAME SUM(S.VALUE)
---------------------------------------------------------------- ------------
redo entries 0
redo size 0
redo log space requests 0
redo log space wait time 0
redo synch writes 0
redo blocks written 0
redo writes 0
# logging or nologging
SYS@ora11g> select logging from dba_tables where table_name = 'REDO_TABLE';
LOG
---
YES
SYS@ora11g> alter table hr.redo_table nologging;
Table altered.
SYS@ora11g> select logging from dba_tables where table_name = 'REDO_TABLE';
LOG
---
NO
SYS@ora11g> select n.name, sum(s.value)
from v$sesstat s, v$statname n
2 3 where n.name in ('redo entries','redo size','redo synch writes','redo writes','redo blocks written',
'redo log space requests','redo log space wait time')
and s.statistic# = n.statistic#
and s. sid = (select sid from v$mystat where rownum = 1)
group by n.name; 4 5 6 7
NAME SUM(S.VALUE)
---------------------------------------------------------------- ------------
redo entries 6
redo size 2340
redo log space requests 0
redo log space wait time 0
redo synch writes 1
redo blocks written 0
redo writes 0
<< NEW SESSION >>
SYS@ora11g> select n.name, sum(s.value)
from v$sesstat s, v$statname n
where n.name in ('redo entries','redo size','redo synch writes','redo writes','redo blocks written',
'redo log space requests','redo log space wait time')
and s.statistic# = n.statistic#
and s. sid = (select sid from v$mystat where rownum = 1)
group by n.name; 2 3 4 5 6 7
NAME SUM(S.VALUE)
---------------------------------------------------------------- ------------
redo entries 0
redo size 0
redo log space requests 0
redo log space wait time 0
redo synch writes 0
redo blocks written 0
redo writes 0
2024/02/08
enq : TX - row lock contention
drop table hr.emp purge;
create table hr.emp
as select * from hr.employees;
[sess_1]
update hr.emp set salary = 2000 where employee_id = 200;
[sess_2]
delete from hr.emp where employee_id = 200;
select sid, serial#, username, blocking_session, event, sql_id, prev_sql_id
from v$session
where event like '%TX%';
----
SID SERIAL# USERNAME BLOCKING_SESSION
---------- ---------- ------------------------------ ----------------
EVENT SQL_ID
---------------------------------------------------------------- -------------
PREV_SQL_ID
-------------
21 2039 HR 186
enq: TX - row lock contention 8suw0gu1fumpv
bmbdnqq9s0m7q
SYS@ora11g> select sql_text from v$sql where sql_id = 'bmbdnqq9s0m7q';
SQL_TEXT
--------------------------------------------------------------------------------
create table hr.emp
SYS@ora11g> select sql_text from v$sql where sql_id = '8suw0gu1fumpv';
SQL_TEXT
--------------------------------------------------------------------------------
delete from hr.emp where employee_id = 200
select sid, serial#, username, blocking_session, event, sql_id, prev_sql_id
from v$session
where sid in (select blocking_session from v$session);
---
186 975 HR SQL*Net message from client 0xwwv6ba9d01c
SYS@ora11g> select * from v$lock where sid in(9,186) and type in ('TX','TM');
ADDR KADDR SID TY ID1 ID2 LMODE REQUEST
-------- -------- ---------- -- ---------- ---------- ---------- ----------
CTIME BLOCK
---------- ----------
F6F1EB28 F6F1EB58 186 TM 88883 0 3 0
881 0
3E3E9074 3E3E90B4 186 TX 131084 2529 6 0
881 1
TYPE : TM
ID1 : object id
TYPE : TX
ID1 : undo segment번호 + transaction slot 번호
ID2 : transaction slot sequence 번호
selct sid, type, id1, id2, lmode, request,
block, to_char(trunc(id1/power(2,16))) usn,
bitland(id1,to_number('ffff','xxxx')) + 0 slot,
id2 sqn
from v$lock
where sid in(9,186) and type in ('TX','TM');
SYS@ora11g> select sid, type, id1, id2, lmode, request,
block, to_char(trunc(id1/power(2,16))) usn,
bitand(id1,to_number('ffff','xxxx')) + 0 slot,
id2 sqn
from v$lock
where sid in(9,186) and type in ('TX','TM'); 2 3 4 5 6
SID TY ID1 ID2 LMODE REQUEST BLOCK
---------- -- ---------- ---------- ---------- ---------- ----------
USN SLOT SQN
---------------------------------------- ---------- ----------
186 TM 88883 0 3 0 0
1 23347 0
186 TX 131084 2529 6 0 1
2 12 2529
SYS@ora11g> select segment_name from dba_rollback_segs where segment_id = 2;
SEGMENT_NAME
------------------------------
_SYSSMU2_1139286244$
SYS@ora11g> select object_name, object_type, data_object_id, object_id
from dba_objects
where object_id = 88883; 2 3
OBJECT_NAME
--------------------------------------------------------------------------------
OBJECT_TYPE DATA_OBJECT_ID OBJECT_ID
------------------- -------------- ----------
EMP
TABLE 88883 88883
select sid, serial#, username, blocking_session, event,
row_wait_obj# as obj_no, -- row 번호
row_wait_file# as file_no, -- 파일
row_wait_block# as block_no, --
row_wait_row# as row_no
from v$session
where event like '%TX%';
----------------------------------------------------------
HR 186 enq: TX - row lock contention 88883 4 539 2
select tablespace_name, file_name
from dba_data_files where file_id =4;
- restricted rowid(v7) 6byte
#block번호.#rowslot번호.#file번호
- extended rowid(8v) 10byte
#object번호(6자리) + #rowslot번호(3자리) + #block번호(6자리) + #rowslot번호(3자리)
SYS@ora11g> select
dbms_rowid.rowid_create(0,88883,4,539,2) as "restricted rowid",
dbms_rowid.rowid_create(1,888 2 83,4,539,2) as "extended rowid"
from dual; 3 4
restricted rowid extended rowid
------------------ ------------------
0000021B.0002.0004 AAAVszAAEAAAAIbAAC
SYS@ora11g> select * from hr.emp where rowid ='AAAVszAAEAAAAIbAAC';
EMPLOYEE_ID FIRST_NAME LAST_NAME
----------- -------------------- -------------------------
EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY
------------------------- -------------------- --------- ---------- ----------
COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
-------------- ---------- -------------
200 Jennifer Whalen
JWHALEN 515.123.4444 17-SEP-03 AD_ASST 9431.79
101 10
select * from hr.emp where rowid ='AAAVszAAEAAAAIbAAC'
[sess_1]
HR@ora11g> delete from hr.emp where employee_id = 200;
1 row deleted.
[sess_2]
HR@ora11g> drop table hr.emp purge;
drop table hr.emp purge
*
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
enq : TX - row lock contention
HR@ora11g> create table hr.unique_test(id number);
Table created.
HR@ora11g> create unique index hr.unique_test_idx on hr.unique_test(id);
Index created.
[sess_1]
HR@ora11g> insert into hr.unique_test(id) values(1);
1 row created.
[sess_2]
HR@ora11g> insert into hr.unique_test(id) values(1);
-- WAITING
transaction 해당하는 행에 대해서 TX LOCK TYPE 이면 LMODE 는 X, 6 수행이 되면서 동일한 값이 다른 세션에서 입력이 된경우 REQUEST S(Share), 4로 대기한다.
select sid, serial#, username, blocking_session, event, sql_id, prev_sql_id
from v$session
where event like '%TX%';
---
21 2039 HR 186 enq: TX - row lock contention cuz65hgum0qns c84ynr1u8hbs2
[sess_1]
HR@ora11g> commit;
Commit complete.
[sess_1]
HR@ora11g> insert into hr.unique_test(id) values(1);
insert into hr.unique_test(id) values(1)
*
ERROR at line 1:
ORA-00001: unique constraint (HR.UNIQUE_TEST_IDX) violated
만약에 session 1에서 rollback을 수행 한 경우에는 session 2 수행 된다.
enq : TX - allocate ITL entry
● 1. 작업
SYS@ora11g> create table hr.itl_table(id number, l_name varchar2(1000), f_name varchar2(1000))
initrans 1 maxtrans 2 pctfree 0;
- ini_trans
: block header에 transaction layer에 생성된 transaction slot의 수- maxtrans
: data가 저장되어 있는 free 공간에 생성될 수 있는 transaction slot의 최대수
, 필요할때 free 공간이 있으면 생성해서 사용하다가 transaction 이 종료되면 자동으로 해제된다.- pct_free
: 최초로 블록에 데이터가 저장될때 pctfree값을 제외 시키고 입력한다.
- 이유?
- 기존ROW들의 증가분때문에 남겨놓은 free 영역
만약에 free 공간이 없으면 다른 블록이 이전을 해야한는 문제가 발생(row migration)- mxxtrans 값을 보장하기 위해서
만약에 transaction slot 생성하지 못하면 트랜잭션을 수행한 세션에서는 대기 하는 일이 발생한다.enq : TX - allocate ITL entry
SYS@ora11g> select ini_trans, max_trans, pct_free from dba_tables
where table_name = 'ITL_TABLE'; 2
INI_TRANS MAX_TRANS PCT_FREE
---------- ---------- ----------
1 255 0
SYS@ora11g> insert into hr.itl_table(id, l_name, f_name)
select level, rpad('x',1000,'x') ,rpad('z','1000','z')
from dual connect by level <= 10;
SYS@ora11g> commit;
SYS@ora11g> select id, rowid, dbms_rowid.rowid_block_number(rowid)
from hr.itl_table order by 3;
-----------------------
5 AAAVs+AAEAAADqjAAA 15011
6 AAAVs+AAEAAADqjAAB 15011
7 AAAVs+AAEAAADqjAAC 15011
8 AAAVs+AAEAAADqjAAD 15011
9 AAAVs+AAEAAADqkAAA 15012
10 AAAVs+AAEAAADqkAAB 15012
1 AAAVs+AAEAAADqnAAA 15015
2 AAAVs+AAEAAADqnAAB 15015
3 AAAVs+AAEAAADqnAAC 15015
4 AAAVs+AAEAAADqnAAD 15015
[sess_1]
HR@ora11g> update hr.itl_table
set l_name = rpad('y',1000,'y'), f_name = rpad('a',1000,'a')
where id = 1;
[sess_2]
HR@ora11g> update hr.itl_table
set l_name = rpad('y',1000,'y'), f_name = rpad('a',1000,'a')
where id = 2;
[sess_3]
HR@ora11g> update hr.itl_table
set l_name = rpad('y',1000,'y'), f_name = rpad('a',1000,'a')
where id = 3;
지금 걸려있는것은 sql_id 로 봐야하고
이미세션이 끝난 blocking_session으로 볼라면 prev_sql_id로 봐야한다.
SYS@ora11g> select sid, serial#, username, blocking_session, event, sql_id, prev_sql_id
from v$session
where event like '%TX%';
---
21 2039 HR 162 enq: TX - allocate ITL entry 2k3c52543mxfb cuz65hgum0qns
- 해결방법
: initrans 값, pct_free 값 조정
rollback;
● 2. enq: TX - allocate ITL entry 해결방법
SYS@ora11g> select ini_trans, max_trans, pct_free from dba_tables
where table_name = 'ITL_TABLE'; 2
INI_TRANS MAX_TRANS PCT_FREE
---------- ---------- ----------
1 255 0
하지만, block parameter 값을 수정한 경우 새로운 block부터 적용된디.
기존블록은 그대로 문제가 생길수잇다.
-> 따라서 재배치 작업해줘야한다.
SYS@ora11g> alter table hr.itl_table initrans 2 pctfree 10;
Table altered.
SYS@ora11g> select ini_trans, max_trans, pct_free from dba_tables
where table_name = 'ITL_TABLE'; 2
INI_TRANS MAX_TRANS PCT_FREE
---------- ---------- ----------
2 255 10
● 3. 재배치 작업 move
기존 block에도 새로운 block parameter 를 적용하려면 테이블을 재구성해야한다.
SYS@ora11g> alter table hr.itl_table move;
Table altered.
👉 하지만
테이블을 재구성하면 기존 행들의 rowid가 바뀐다.
인덱스는 다시 재구성해줘야한다.
● 4. rebuild 작업 해야한다.
온라인중에 index하는것
alter index 인덱스명 rebuild online;
많은 양의 데이터 검색시 유용함
첫번째 블록 부터 마지막 사용한 블록 (high water mark)까지 읽어오는 방식
Multi Block I/O 발생한다.
한번에 I/O call 방식할때 db_file_multiblock_read_count 설정되어 있는 블록 수 만큼 읽어 들이기 위해서는 extent 안에 db_file_multiblock_read_count 설정되어 있는 갯수 만큼 있을 경우에 나름 Multi Block I/O를 성능이 좋을수 있다.
만약 db_file_multiblock_read_count 크기가 128이고
extent가 64k 블록크기가 8k이면
-> 8개씩 2번 올려진다.
SYS@ora11g> show parameter db_file_multiblock_read_count
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_file_multiblock_read_count integer 123
full table scan으로 인해서 과도한 multi block I/O 발생시에 나온다.
- db file scattered read wait event 발생 할 수 있다.
따라서 v$seession 모니터링은 필수이다.
불필요한 redo buffer에 기록할것은 (ex)insert)
hint를 작성헤서 no logging모드로 작성
HR@ora11g> set autot traceonly exp
1 - filter("EMPLOYEE_ID"=100)
: 처음부터 끝까지(high water mark)까지 스캔하겠다.
HR@ora11g> select * from hr.emp where employee_id=100;
Execution Plan
----------------------------------------------------------
Plan hash value: 3956160932
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 133 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 133 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("EMPLOYEE_ID"=100)
Note
-----
- dynamic sampling used for this statement (level=2
LGWR 할때
controlfile을 보고
current한 그룹
- full table scan 속도를 개선
- 병렬 작업 (parallel 또는 parallel_index 힌트를 사용)
병렬처리하면 data buffer cache를 덜 access한다- db_file_multiblock_read_count 파라미터의 값을 조정하자.
alter session set db_file_multiblock_read_count=128;
select /*+ full(e) parallel(e,2) */ * from emp e;
select employee_id, rowid,
dbms_rowid.rowid_object(rowid) as data_object_id,
dbms_rowid.rowid_relative_fno(rowid) as file_no,
dbms_rowid.rowid_block_number(rowid) as block_no,
dbms_rowid.rowid_row_number(rowid) as row_slot_no
from hr.emp;
-------------------------------------------
198 AAAVtDAAEAAAAIrAAA 88899 4 555 0
199 AAAVtDAAEAAAAIrAAB 88899 4 555 1
200 AAAVtDAAEAAAAIrAAC 88899 4 555 2
201 AAAVtDAAEAAAAIrAAD 88899 4 555 3
202 AAAVtDAAEAAAAIrAAE 88899 4 555 4
HR@ora11g> set autot traceonly exp
HR@ora11g> select * from hr.emp where rowid ='AAAVtDAAEAAAAIrAAA';
Execution Plan
----------------------------------------------------------
Plan hash value: 1116584662
--------------------------------------------------------------------------------
---
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------
---
| 0 | SELECT STATEMENT | | 1 | 69 | 1 (0)| 00:00:0
1 |
| 1 | TABLE ACCESS BY USER ROWID| EMP | 1 | 69 | 1 (0)| 00:00:0
1 |
--------------------------------------------------------------------------------
---
참고자료
: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=gglee0127&logNo=221336088285
:B-tree 인덱스의 가장 일반적이고 정상적인 형태의 액세스 방식입니다. 인덱스 루트에서 리프 블록까지 수직적 탐색을 하고, 필요한 범위만큼 수평적 탐색하는 스캔 방식입니다.
인덱스를 Range Scan 하려면, 선두 컬럼을 가공하지 않은 상태로 조건절에 사용해야 합니다.
인덱스
root block -> branch block -> leaf block 까지
수직으로 탐색한 후 leaf block 에서 필요한 범위만 스캔 방식 (one plus one scan)
- one plus one scan
: root->branch->leaf->block의 rowid로 cursor에 올려놓고 ->leaf 다른것없는지확인
다 latch를 잡는다.
=> 성능이 떨어진다.그래서 오라클에서 해결
: 다시 어차피 latch를 할꺼니깐
buffer pinning 을 잡는다
latch점유시간 줄이고, I/O 성능이 좋아진다.
HR@ora11g> create index hr.emp_idx on hr.emp(employee_id);
Index created.
HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name
from user_indexes ix, user_ind_columns ic
where ix.index_name = ic.index_name
and ix.table_name = 'EMP'; 2 3 4
INDEX_NAME UNIQUENES
------------------------------ ---------
COLUMN_NAME
--------------------------------------------------------------------------------
EMP_IDX NONUNIQUE
EMPLOYEE_ID
HR@ora11g> select * from hr.emp where employee_id = 100;
Execution Plan
----------------------------------------------------------
Plan hash value: 1472992808
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_IDX | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPLOYEE_ID"=100)
HR@ora11g> set autot off
HR@ora11g> set autot traceonly exp
HR@ora11g> select /*+ full(e) */ * from hr.emp e where employee_id =100;
Execution Plan
----------------------------------------------------------
Plan hash value: 3956160932
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 69 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("EMPLOYEE_ID"=100)
HR@ora11g> select * from hr.emp where to_number(employee_id) = 100;
Execution Plan
----------------------------------------------------------
Plan hash value: 3956160932
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 69 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_NUMBER(TO_CHAR("EMPLOYEE_ID"))=100)
- index 유도하는 hint
HR@ora11g> select /*+ index(e emp_idx) */ * from hr.emp e where employee_id =100;
- index range scan으로 유도하는 hint
HR@ora11g> select /*+ index(e emp_idx) */ * from hr.emp e where employee_id =100;
Execution Plan
----------------------------------------------------------
Plan hash value: 1472992808
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_IDX | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPLOYEE_ID"=100)
HR@ora11g> select /*+ index_rs(e emp_idx) */ * from hr.emp e where employee_id =100;
Execution Plan
----------------------------------------------------------
Plan hash value: 1472992808
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_IDX | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPLOYEE_ID"=100)
HR@ora11g> drop index hr.emp_idx;
Index dropped.
HR@ora11g> create unique index hr.emp_idx on hr.emp(employee_id);
Index created.
HR@ora11g> alter table hr.emp add constraint emp_id_pk primary key(employee_id) using index hr.emp_idx;
Table altered.
HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name
from user_indexes ix, user_ind_columns ic
where ix.index_name = ic.index_name
and ix.table_name = 'EMP'; 2 3 4
INDEX_NAME UNIQUENESS
------------------------------ ------------------------------
COLUMN_NAME
--------------------------------------------------------------------------------
EMP_IDX UNIQUE
EMPLOYEE_ID
HR@ora11g> col column_name format a15
HR@ora11g> select c.column_name, u.constraint_name,
u.constraint_type, u.search_condition, u.index_name
from user_constraints u, user_cons_columns c
where u.constraint_name = c.constraint_name
and u.table_name ='EMP'; 2 3 4 5
COLUMN_NAME CONSTRAINT_NAME C SEARCH_CONDITION INDEX_NAME
--------------- ------------------------------ - -------------------------------------------------------------------------------- ------------------------------
LAST_NAME SYS_C0011586 C "LAST_NAME" IS NOT NULL
EMAIL SYS_C0011587 C "EMAIL" IS NOT NULL
HIRE_DATE SYS_C0011588 C "HIRE_DATE" IS NOT NULL
JOB_ID SYS_C0011589 C "JOB_ID" IS NOT NULL
EMPLOYEE_ID EMP_ID_PK P EMP_IDX
= 사용할때만 사용된다: Index Unique Scan은 수직적 탐색으로만 데이터를 찾는 스캔 방식입니다. Unique 인덱스를 '=' 조건으로 탐색하는 경우에 동작합니다. Unique 인덱스가 존재하는 컬럼은 중복 값이 존재하지 않습니다. 따라서 동일('=')하다는 조건으로 검색할 때, 데이터를 한 건 찾고 나서는 더 이상 탐색할 필요가 없습니다.
: Unique 인덱스라고 해도 범위(between, >, <, like) 조건으로 검색하게 되면, Index Range Scan으로 처리됩니다. 수직적 탐색으로만 해당 레코드를 찾을 수 없기 때문입니다.
HR@ora11g> set autot traceonly exp
HR@ora11g> select * from hr.emp where employee_id =100;
Execution Plan
----------------------------------------------------------
Plan hash value: 2466118986
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | EMP_IDX | 1 | | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPLOYEE_ID"=100)
: root ~ leaf 까지 쿼리를 분리해서 실행계획을 만든다.
ex) where emp_id in (1,100) 이면 2개만들어진다.
👉 loop ~ leaf 까지 너무길면 실행계획이 range scan 나온다
이때 => index를 재배치(rebuild) 해야한다.
HR@ora11g> select * from hr.emp where employee_id in (100,200);
Execution Plan
----------------------------------------------------------
Plan hash value: 1651504986
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 138 | 2 (0)| 00:00:01 |
| 1 | INLIST ITERATOR | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| EMP | 2 | 138 | 2 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | EMP_IDX | 2 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("EMPLOYEE_ID"=100 OR "EMPLOYEE_ID"=200)
HR@ora11g> select * from hr.emp where employee_id = 100
union all
select * from hr.emp where employee_id = 200; 2 3
Execution Plan
----------------------------------------------------------
Plan hash value: 1345714510
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 138 | 2 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 1 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | EMP_IDX | 1 | | 0 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 1 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | EMP_IDX | 1 | | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("EMPLOYEE_ID"=100)
5 - access("EMPLOYEE_ID"=200)
HR@ora11g> select * from hr.emp where employee_id = 100
union
select * from hr.emp where employee_id = 200; 2 3
Execution Plan
----------------------------------------------------------
Plan hash value: 28680880
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 138 | 4 (50)| 00:00:01 |
| 1 | SORT UNIQUE | | 2 | 138 | 4 (50)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 1 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | EMP_IDX | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 1 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | EMP_IDX | 1 | | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("EMPLOYEE_ID"=100)
6 - access("EMPLOYEE_ID"=200)
HR@ora11g> create index hr.emp_name_idx on hr.emp(last_name);
Index created.
HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name
from user_indexes ix, user_ind_columns ic
where ix.index_name = ic.index_name
and ix.table_name = 'EMP'; 2 3 4
INDEX_NAME UNIQUENESS COLUMN_NAME
------------------------------ ------------------------------ ---------------
EMP_IDX UNIQUE EMPLOYEE_ID
EMP_NAME_IDX NONUNIQUE LAST_NAME
HR@ora11g> select * from hr.emp where last_name = 'KING';
Execution Plan
----------------------------------------------------------
Plan hash value: 2039216739
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_NAME_IDX | 1 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("LAST_NAME"='KING')
HR@ora11g> select * from hr.emp where last_name = 'KING' and first_name= 'Steven';
Execution Plan
----------------------------------------------------------
Plan hash value: 2039216739
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_NAME_IDX | 1 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("FIRST_NAME"='Steven')
2 - access("LAST_NAME"='KING')
HR@ora11g> drop index hr.emp_name_idx;
Index dropped.
- 조합 인덱스 생성
: 혼자서도 자주사용되는 컬럼을 선행으로HR@ora11g> create index hr.emp_name_idx on hr.emp(last_name, first_name);
HR@ora11g> select ix.index_name, ix.uniqueness, ic.column_name, ic.column_position
from user_indexes ix, user_ind_columns ic
where ix.index_name = ic.index_name
and ix.table_name = 'EMP'; 2 3 4
INDEX_NAME UNIQUENESS COLUMN_NAME COLUMN_POSITION
------------------------------ ------------------------------ --------------- ---------------
EMP_IDX UNIQUE EMPLOYEE_ID 1
EMP_NAME_IDX NONUNIQUE FIRST_NAME 2
EMP_NAME_IDX NONUNIQUE LAST_NAME 1
HR@ora11g> select * from hr.emp where last_name = 'KING' and first_name= 'Steven';
Execution Plan
----------------------------------------------------------
Plan hash value: 2039216739
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 69 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_NAME_IDX | 1 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("LAST_NAME"='KING' AND "FIRST_NAME"='Steven')
: Index Full Scan은 수직적 탐색 없이 인덱스 리프 블록 처음부터 끝까지 수평적으로 탐색하는 방식입니다. 데이터 검색을 위한 최적의 인덱스가 없을 때 차선으로 선택됩니다.
테이블(여러개의 블록)보다 상대적으로 인덱스가 작은 블록수로 만들어졌으므로 index full scan 이 효율적이다.
HR@ora11g> select last_name, first_name from hr.emp;
Execution Plan
----------------------------------------------------------
Plan hash value: 1448724716
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 107 | 1605 | 1 (0)| 00:00:01 |
| 1 | INDEX FULL SCAN | EMP_NAME_IDX | 107 | 1605 | 1 (0)| 00:00:01 |
HR@ora11g> select count(*) from hr.emp;
Execution Plan
----------------------------------------------------------
Plan hash value: 660937672
--------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
--------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | INDEX FULL SCAN| EMP_IDX | 107 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------
HR@ora11g> select /*+ index_ffs(e emp_idx) */ count(*) from hr.emp e;
Execution Plan
----------------------------------------------------------
Plan hash value: 3644266057
-------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | INDEX FAST FULL SCAN| EMP_IDX | 107 | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------
index full scan
- single block i/o
- 정렬보장
- 속도 느림
- db file sequential read
index fast full scan
- multi block i/o
- 정렬 안됨
- 속도 빠름
- db file scattered read
👉 index full scan 보다 index fast full scan이 더 빠른이유?
HR@ora11g> select /*+ index_ffs(e emp_idx) parallel_index(e, emp_idx, 2) */ count(*) from hr.emp e;
Execution Plan
----------------------------------------------------------
Plan hash value: 518463889
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 | | | |
| 1 | SORT AGGREGATE | | 1 | | | | | |
| 2 | PX COORDINATOR | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10000 | 1 | | | Q1,00 | P->S | QC (RAND) |
| 4 | SORT AGGREGATE | | 1 | | | Q1,00 | PCWP | |
| 5 | PX BLOCK ITERATOR | | 107 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
| 6 | INDEX FAST FULL SCAN| EMP_IDX | 107 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
-----------------------------------------------------------------------------------------------------------