Oracle-索引分裂研究

2022-03-11 00:00:00 索引 数据 节点 等待 分裂




索引分裂(Index Block Split),就是索引块的分裂。当一次DML操作修改了索引块上的数据,但是旧有的索引块没有足够的空间去容纳新修改的数据时,将分裂出一个新的索引块,旧有块的部分数据放到新开辟的索引块上去,这个过程就称为索引块的分裂,简称索引分裂。

在分裂的过程中,前台进程需要等待分裂完成之后才能继续操作。如果此时其它会话也要修改这个索引块的数据,那么将会出现索引块的竞争,等待以“enq: TX – index contention”的形式体现,该事件是一个与索引分裂直接相关的等待事件。一般索引块的分裂持有资源和释放非常迅速,并不会对数据库造成严重的性能影响,但是在表操作并发量很大的情况下可能导致严重的竞争。当索引分裂发生时,负责实施分裂的进程会持有相关的队列锁,直到该进程完成分裂操作才会释放该队列锁。在这个过程中负责分裂的进程需要找到合适的新块并将对应的数据移动到该新块中。若在此分裂的过程中,有其它进程INSERT数据到该索引块中,则将进入enq: TX – index contention等待,直到分裂结束锁被释放。



分类

索引分裂有如下几种情况:

(1)按照分裂对象分:

  • 叶子节点分裂:**叶子节点上没有足够的空间容纳新插入的数据。叶子节点分裂的情况频繁发生,对性能影响直接。

  • 枝节点分裂:**其下层的节点分裂,会导致在该节点上增加一条记录指向新加的节点,当该节点空间不足时,会发生分裂。

  • 根节点分裂:**特殊的枝节点分裂,分裂需要两个新的数据块,将原有数据转移至两个新节点,原有节点上生成两条记录分别指向新增的数据块。

(2)按照分裂数据块比例分:

  • 9-1分裂:**当事务向索引的右侧的叶节点上插入一条大于或等于现有索引块上大值的数据,且该索引块上不存在其它未提交的事务,如果没有足够的空间,那么就会发生9-1分裂。绝大部分数据还保留在旧有节点上,仅有非常少的一部分数据迁移到新节点上。

  • 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                      
        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                      
        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                      
        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                      
        2 root node splits                   8

Trace 数据统计

col value for a80
select value from v$diag_info where name = 'Default Trace File';
VALUE
--------------------------------------------------------------------------------
/u01/app/oracl
e/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  
        1 leaf node 90-10 splits          2208           2300  92 <======索引分裂
        1 leaf node splits               12525          12713  188
        1 queue splits                                      
        1 root node splits                   6              6  
        2 branch node splits                32             32  
        2 leaf node 90-10 splits           887            887  
        2 leaf node splits                7273           7351  78
        2 queue splits                                    
        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

测试分析

统计测试前后的enq: TX - index contention差集

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的新特性中,Oracle真的用心良苦。


墨天轮原文链接:https://www.modb.pro/db/85028(复制链接至浏览器或点击文末阅读原文查看)

关于作者

张帅,工作9年,其中DBA相关工作经验5年。中国DBA联盟成员,拥有OCP、OBCA证书。现负责公司Oracle、MySQL数据库方面的技术工作;负责Weblogic、WAS、Tomcat等中间件方面的技术支持;掌握SQL编程、Python编程,以及各种压力测试工具和ETL工具使用方法。服务的客户包含银行、证券、医疗、政府等单位。


END




相关文章