索引分裂介紹
索引分裂(Index Block Split),就是索引塊的分裂。當一次DML操作修改了索引塊上的資料,但是舊有的索引塊沒有足夠的空間去容納新修改的資料時,將分裂出一個新的索引塊,舊有塊的部分資料放到新開闢的索引塊上去,這個過程就稱為索引塊的分裂,簡稱索引分裂。
在分裂的過程中前臺程序需要等待分裂完成之後才能繼續操作。如果此時其它會話也要修改這個索引塊的資料,那麼將會出現索引塊的競爭,等待以“enq: TX – index contention”的形式體現,該事件是一個與索引分裂直接相關的等待事件。一般索引塊的分裂持有資源和釋放非常迅速,並不會對資料庫造成嚴重的效能影響,但是對錶操作併發量很大的情況下可能導致嚴重的競爭。當索引分裂發生時,負責實施分裂的程序會持有相關的佇列鎖,直到該程序完成分裂操作才會釋放該佇列鎖。在這個過程中負責分裂的程序需要找到合適的新塊並將對應的資料移動到該新塊中。若在此分裂的過程中,有其它程序INSERT資料到該索引塊中,則將進入enq: TX – index contention等待,直到分裂結束鎖被釋放。
分類
索引分裂有如下幾種情況
(1)按照分裂物件分:
l 葉子節點分裂:葉子節點上沒有足夠的空間容納新插入的資料。葉子節點分裂的情況最頻繁發生,對效能影響最直接。
l 枝節點分裂:其下層的節點分裂,會導致在該節點上增加一條記錄指向新加的節點,當該節點空間不足時,會發生分裂。
l 根節點分裂:特殊的枝節點分裂,分裂需要兩個新的資料塊,將原有資料轉移至兩個新節點,原有節點上生成兩條記錄分別指向新增的資料塊。
(2)按照分裂資料塊比例分:
l 9-1分裂:當事務向索引的最右側的葉節點上插入一條大於或等於現有索引塊上最大值的資料,且該索引塊上不存在其它未提交的事務,如果沒有足夠的空間,那麼就會發生9-1分裂。絕大部分資料還保留在舊有節點上,僅有非常少的一部分資料遷移到新節點上。
l 5-5分裂:當發生5-5分裂時,有一半索引記錄仍存在當前塊,而另一半資料移動到新的節點中,舊節點和新節點上的資料比例幾乎是持平的。5-5分裂發生的條件:
1、當左側節點發生新值插入時(插入到葉子節點中的索引鍵值小於該塊中的最大值)。
2、當發生DML操作時,索引塊上沒有足夠空間分配新的ITL槽。
3、當新插入資料大於或等於索引中最大值時,但是資料塊上還存在其它未提交的事務。
對效能來說,無論是9-1分裂,還是5-5分裂,都會影響系統的效能。通過10224事件可以生成索引塊分裂及刪除的
索引分裂實驗
基礎環境準備
--建立使用者表空間
create tablespace zsdba_data datafile '+DATA' size 200M;
create user autoidx identified by autoidx default tablespace zsdba_data;
grant dba to autoidx;
--建立表
create table zsdba_idx(id number(20) not null,name varchar(20));
alter table zsdba_idx add constraint pri_id primary key (id);
基礎資訊統計--之前
--基礎資訊統計
col owner for a15
col segment_name for a15
col segment_type for a15
set linesize 200
set pagesize 999
select t.owner,t.segment_name,t.segment_type,t.header_file,t.header_block from dba_segments t where t.segment_name in ('ZSDBA_IDX','PRI_ID');
OWNER SEGMENT_NAME SEGMENT_TYPE HEADER_FILE HEADER_BLOCK
--------------- --------------- --------------- ----------- ------------
AUTOIDX PRI_ID INDEX 2 138
AUTOIDX ZSDBA_IDX TABLE 2 130
select t.owner,t.segment_name,t.segment_type,t.file_id,t.block_id,t.blocks from dba_extents t where t.segment_name in ('ZSDBA_IDX','PRI_ID');
OWNER SEGMENT_NAME SEGMENT_TYPE FILE_ID BLOCK_ID BLOCKS
--------------- --------------- --------------- ---------- ---------- ----------
AUTOIDX PRI_ID INDEX 2 136 8
AUTOIDX ZSDBA_IDX TABLE 2 128 8
col name for a25
select t.inst_id,t.name,t.value from gv$sysstat t where t.NAME like '%splits%' order by t.inst_id,t.name;
INST_ID NAME VALUE
---------- ------------------------- ----------
1 branch node splits 34
1 leaf node 90-10 splits 2208
1 leaf node splits 12525
1 queue splits 0
1 root node splits 6
2 branch node splits 32
2 leaf node 90-10 splits 887
2 leaf node splits 7273
2 queue splits 0
2 root node splits 8
資料插入
通過10224事件可以生成索引塊分裂及刪除的trace
alter session set events '10224 TRACE NAME CONTEXT FOREVER,LEVEL 10';
alter session set tracefile_identifier="STACK_10224";
insert into zsdba_idx select level,'11' from dual connect by level<50000;
commit;
alter session set events '10224 TRACE NAME CONTEXT OFF';
基礎資訊統計--之後
col owner for a15
col segment_name for a15
col segment_type for a15
set linesize 200
set pagesize 999
select t.owner,t.segment_name,t.segment_type,t.header_file,t.header_block,t.blocks,t.bytes
from dba_segments t where t.segment_name in ('T_IBS_LHR','PRI_ID');
OWNER SEGMENT_NAME SEGMENT_TYPE HEADER_FILE HEADER_BLOCK BLOCKS BYTES
--------------- --------------- --------------- ----------- ------------ ---------- ----------
AUTOIDX PRI_ID INDEX 2 138 112 917504
select t.owner,t.segment_name,t.segment_type,t.file_id,t.block_id,t.blocks from dba_extents t where t.segment_name in ('ZSDBA_IDX','PRI_ID');
OWNER SEGMENT_NAME SEGMENT_TYPE FILE_ID BLOCK_ID BLOCKS
--------------- --------------- --------------- ---------- ---------- ----------
AUTOIDX PRI_ID INDEX 2 136 8
AUTOIDX PRI_ID INDEX 2 144 8
AUTOIDX PRI_ID INDEX 2 160 8
AUTOIDX PRI_ID INDEX 2 176 8
AUTOIDX PRI_ID INDEX 2 192 8
AUTOIDX PRI_ID INDEX 2 200 8
AUTOIDX PRI_ID INDEX 2 216 8
AUTOIDX PRI_ID INDEX 2 232 8
AUTOIDX PRI_ID INDEX 2 248 8
AUTOIDX PRI_ID INDEX 2 264 8
AUTOIDX PRI_ID INDEX 2 280 8
AUTOIDX PRI_ID INDEX 2 296 8
AUTOIDX PRI_ID INDEX 2 312 8
AUTOIDX PRI_ID INDEX 2 328 8
AUTOIDX ZSDBA_IDX TABLE 2 128 8
AUTOIDX ZSDBA_IDX TABLE 2 152 8
AUTOIDX ZSDBA_IDX TABLE 2 168 8
AUTOIDX ZSDBA_IDX TABLE 2 184 8
AUTOIDX ZSDBA_IDX TABLE 2 208 8
AUTOIDX ZSDBA_IDX TABLE 2 224 8
AUTOIDX ZSDBA_IDX TABLE 2 240 8
AUTOIDX ZSDBA_IDX TABLE 2 256 8
AUTOIDX ZSDBA_IDX TABLE 2 272 8
AUTOIDX ZSDBA_IDX TABLE 2 288 8
AUTOIDX ZSDBA_IDX TABLE 2 304 8
AUTOIDX ZSDBA_IDX TABLE 2 320 8
AUTOIDX ZSDBA_IDX TABLE 2 336 8
27 rows selected.
col name for a25
select t.inst_id,t.name,t.value from gv$sysstat t where t.NAME like '%splits%' order by t.inst_id,t.name;
INST_ID NAME VALUE
---------- ------------------------- ----------
1 branch node splits 34
1 leaf node 90-10 splits 2300
1 leaf node splits 12713
1 queue splits 0
1 root node splits 6
2 branch node splits 32
2 leaf node 90-10 splits 887
2 leaf node splits 7351
2 queue splits 0
2 root node splits 8
Trace 資料統計
col value for a80
select value from v$diag_info where name = 'Default Trace File';
VALUE
--------------------------------------------------------------------------------
/u01/app/oracle/diag/rdbms/orcl/orcl1/trace/orcl1_ora_48064_STACK_10224.trc
[oracle@19db1:/home/oracle]$ grep 'splitting' orcl1_ora_48064_STACK_10224.trc
splitting leaf,dba 0x0080008b,time 16:59:43.374
splitting leaf,dba 0x0080008c,time 16:59:43.381
.....
[oracle@19db1:/home/oracle]$ grep 'splitting' orcl1_ora_48064_STACK_10224.trc|awk -F '[ |,]' '{print $4}'
0x0080008b
............
0x0080014f
[oracle@19db1:/u01/app/oracle/diag/rdbms/orcl/orcl1/trace]$ grep 'splitting' orcl1_ora_48064_STACK_10224.trc|awk -F '[ |,]' '{print $4}'|uniq|wc -l
92 <=========索引分裂次數
資料分析
索引PRI_ID之dba_extents檢視
從索引pri_id的現有塊數看,從1個extent擴充套件至14個extent,目前1個extent有8個block,索引pri_id目前有112個塊,和dba_segments檢視統計一致。
資料為有序插入,會產生型別為leaf node 90-10 splits的分裂,即分裂塊次數最多有111次。
索引PRI_ID之gv$sysstat檢視
從檢視gv$sysstat的leaf node 90-10 splits統計值看,插入前後差值為92,小於111次,且等於trace檔案中統計到的92次。
INST_ID NAME 插入前 插入後 差集
---------- ------------------------- ---------- ---------- ----------
1 branch node splits 34 34 0
1 leaf node 90-10 splits 2208 2300 92 <======索引分裂
1 leaf node splits 12525 12713 188
1 queue splits 0 0 0
1 root node splits 6 6 0
2 branch node splits 32 32 0
2 leaf node 90-10 splits 887 887 0
2 leaf node splits 7273 7351 78
2 queue splits 0 0
2 root node splits 8 8
索引分裂衍生-enq:TX-index contention
enq:TX-index contention是一個非常常見的等待事件,其專指由於索引分裂產生的競爭等待。最常見的索引競爭一般發生在主鍵索引上,主鍵值從序列(sequence)中獲取,每個事務都會生成一條新的記錄,每條記錄都要獲得一個新的序列號,因為從sequence中取出的值是單向遞增的,當索引中插入資料,並且維護索引結構的時候,不得不一直走向索引的最右側的分支,對於每一個操作,都會想要維護索引中最右邊的葉節點,那麼所有的操作都會關注同一個記憶體塊,希望能夠維護這塊記憶體,這就是一種典型的競爭形式。但在同一時間,只有一個人能夠修改這塊記憶體,因此當有一個人在修改的時候,其他所有想修改的人只能處於等待狀態。
下面通過建立正常序列作為索引和18C的擴充套件序列作為索引,驗證18C的擴充套件序列的優勢。
正常序列索引演示
資料準備
conn autoidx/autoidx
drop table zsdba_idx_seq_normal purge;
-- create table
create table zsdba_idx_seq_normal(id number(20) not null,name varchar(20));
-- create/recreate primary, unique and foreign key constraints
alter table zsdba_idx_seq_normal add constraint pri_id_normal primary key (id);
drop sequence test_seq_normal;
create sequence test_seq_normal
minvalue 1
maxvalue 9999999999999999999
start with 1
increment by 1
cache 2;
create or replace procedure p_task_idx_seq_normal is
begin
for i in 1 .. 50000 loop
insert into zsdba_idx_seq_normal values(test_seq_normal.nextval,i);
end loop;
end;
/
enq: TX - index contention事件統計--之前
col inst_id for 999
col event for a35
col total_waits for 999999999
col time_waited_micro for 99999999
set linesize 200
select t.inst_id,t.event,t.total_waits,t.time_waited_micro from gv$system_event t where t.event = 'enq: TX - index contention';
INST_ID EVENT TOTAL_WAITS TIME_WAITED_MICRO
------- ----------------------------------- ----------- -----------------
2 enq: TX - index contention 14167 23440717
1 enq: TX - index contention 22364 42026275
測試資料執行
declare
v_job_no number;
begin
for v_parallel in 1 .. 10 loop
dbms_job.submit(job=>v_job_no,what=>'p_task_idx_seq_normal;');
commit;
end loop;
end;
/
enq: TX - index contention事件統計--之後
col inst_id for 999
col event for a35
col total_waits for 999999999
col time_waited_micro for 99999999
set linesize 200
select t.inst_id,t.event,t.total_waits,t.time_waited_micro from gv$system_event t where t.event = 'enq: TX - index contention';
INST_ID EVENT TOTAL_WAITS TIME_WAITED_MICRO
------- ----------------------------------- ----------- -----------------
1 enq: TX - index contention 25883 51053826
2 enq: TX - index contention 17838 32869104
測試分析
INST_ID EVENT 插入前 插入後 插入前 插入後
等待數 等待數 等待時間 等待時間
------- --------------------------- -------- ------------------- ------------
2 enq: TX - index contention 14167 17838 23440717 32869104
1 enq: TX - index contention 22364 25883 42026275 51053826
enq: TX - index contention等待次數(25883+17838)-(22364+14167)=7190
enq: TX - index contention等待時間(51053826+32869104)-(42026275+23440717)=18455938
擴充套件序列索引演示
資料準備
conn autoidx/autoidx
drop table zsdba_idx_seq_scale purge;
-- create table
create table zsdba_idx_seq_scale(id number(20) not null,name varchar(20));
-- create/recreate primary, unique and foreign key constraints
alter table zsdba_idx_seq_scale add constraint pri_id_scale primary key (id);
drop sequence test_seq_scale;
create sequence test_seq_scale
minvalue 1
maxvalue 9999999999999999999
start with 1
increment by 1
cache 2
scale;
create or replace procedure p_task_idx_seq_scale is
begin
for i in 1 .. 50000 loop
insert into zsdba_idx_seq_scale values(test_seq_scale.nextval,i);
end loop;
end;
/
enq: TX - index contention事件統計--之前
col inst_id for 999
col event for a35
col total_waits for 999999999
col time_waited_micro for 99999999
set linesize 200
select t.inst_id,t.event,t.total_waits,t.time_waited_micro from gv$system_event t where t.event = 'enq: TX - index contention';
INST_ID EVENT TOTAL_WAITS TIME_WAITED_MICRO
------- ----------------------------------- ----------- -----------------
2 enq: TX - index contention 17966 32976462
1 enq: TX - index contention 25920 51084690
測試資料執行
declare
v_job_no number;
begin
for v_parallel in 1 .. 10 loop
dbms_job.submit(job=>v_job_no,what=>'p_task_idx_seq_scale;');
commit;
end loop;
end;
/
enq: TX - index contention事件統計--之後
col inst_id for 999
col event for a35
col total_waits for 999999999
col time_waited_micro for 99999999
set linesize 200
select t.inst_id,t.event,t.total_waits,t.time_waited_micro from gv$system_event t where t.event = 'enq: TX - index contention';
INST_ID EVENT TOTAL_WAITS TIME_WAITED_MICRO
------- ----------------------------------- ----------- -----------------
2 enq: TX - index contention 18374 34443728
1 enq: TX - index contention 26050 53756689
測試分析
統計測試前後的enq: TX - index contention差集
INST_ID EVENT 插入前 插入後 插入前 插入後
等待數 等待數 等待時間 等待時間
------- ----------------------------------- --------------- ------- ---------
2 enq: TX - index contention 17966 18374 32976462 34443728
1 enq: TX - index contention 25920 26050 51084690 53756689
enq: TX - index contention等待次數(26050+18374)-(25920+17966)=538
enq: TX - index contention等待時間(53756689+34443728)-(51084690+32976462)=4139265
總結
從測試資料分析,正常序列作為索引,在高併發的場景下,enq: TX - index contention等待次數7190,等待時間18.5s,擴充套件序列作為索引,在高併發的場景下,enq: TX - index contention等待次數538,等待時間4s,無論是從等待次數和等待時間都有大幅度的提升,而且隨著併發的增大,擴充套件序列的優勢會更加擴大化,由此可見,在18C的新特性中,開發商真的用心良苦。