索引分裂:90-10分裂
数据库的索引基于B-树的结构,通过将数据插入到对应的叶子节点中实现数据新增的功能。更新值的操作是通过"删除"(将值标记为删除)原有的值并在对应的叶子节点上添加修改后的值来实现的。
举个例子,假设现在有一个索引键,并且假设一个数据块只能容纳该索引的4个索引值,然后当前的索引值包含以下这些数据:
Block 1 : Abacus Carrot Block 2 : Dandelion Elephant Heat Iridium Block 3 : Lamb Militant Tight
此时,如果想要往这个索引中添加新的索引值 “Fear”, Oracle 需要进行以下的操作步骤:
- 定位正确的插入数据块 (按首字母 a.b.c.d.. 排列,定位的是第二个数据块)
- 验证新的值是否可以插入到步骤一种定位的数据块中 (每个块只能插入4个值)
- 将新的值插入到数据块中或者将数据块进行分裂操作
此时,因为数据块2已经有4个索引值,因此需要做一次索引分裂的操作,分裂后的索引值的分布情况如下:
Block 1 : Abacus Carrot Block 2 : Dandelion Elephant Block 3 : Fear Heat Iridium Block 4 : Lamb Militant Tight
这个过程就是一个常规的“50-50分裂”的索引分裂过程。
而与此相对应的,是这篇文章要介绍的“90-10分裂”的过程。90-10分裂发生在当新插入的值是当前“最右”的值的时候。简单的说,当这个索引是一个自动正常的类型,比如是一个序列,交易订单的id或者是一个日期类型,交易订单的日期,在这种新插入值总是比当前存在的值都要大的情况下,就会发生90-10分裂,此时,oracle会把“最右”的值复制到分裂产生的新的数据块中。
查询索引分裂产生的数据块的信息,可以通过查询 V$SYSSTAT / V$SESSTAT 表中的 "leaf node splits" 的值来分析。值得注意的是,表中的 leaf node 90-10 splits 这个字段的值,实际上是属于leaf node splits的一个子集,即是说,leaf node splits 的值代表了索引分裂的90-10分裂和50-50分裂的信息。
下面将通过新建一张表,并通过几轮数据新增的操作来展示索引分裂的过程。
Table dropped. SQL> SQL> -- REM create the demo table SQL> create table demo_ibs_90_10 ( 2 employee_id number not null, 3 country_name varchar2(10) not null, 4 dept_name varchar2(18) not null, 5 employee_name varchar2(128) not null, 6 join_date date) 7 / Table created.
创建表的索引
SQL> -- create the index with a PCTFREE of 1 to pack it tightly SQL> create unique index demo_ibs_90_10_u1 on demo_ibs_90_10 (employee_id,country_name,employee_name) pctfree 1; Index created. SQL>
对表进行新增数据操作
SQL> delete source_table; 50653 rows deleted. SQL> insert into source_table select * from dba_objects where object_id is not null; 50653 rows created. SQL> select max(object_id) from source_table; MAX(OBJECT_ID) -------------- 53214 SQL>
然后创建一个新的会话,并进行数据插入操作,并对插入操作进行分析。
SQL> connect scott/tiger Connected. SQL> insert into demo_ibs_90_10 2 select object_id, substr(owner,1,10),substr(object_type,1,18),rpad(object_name,20,dbms_random.string(‘X‘,3)),created 3 from source_table 4 where object_id is not null 5 order by object_id 6 / 50653 rows created. SQL> commit; Commit complete.
对插入数据进行分析
SQL> SQL> select sn.name, ms.value 2 from v$statname sn, v$mystat ms 3 where sn.statistic#=ms.statistic# 4 and sn.name like ‘%leaf node %‘ 5 order by 1 6 / NAME VALUE ---------------------------------------------------------------- ---------- leaf node 90-10 splits 262 leaf node splits 262 SQL> SQL> exec dbms_stats.gather_table_stats(‘‘,‘DEMO_IBS_90_10‘,estimate_percent=>100,cascade=>TRUE); PL/SQL procedure successfully completed. SQL> select num_rows, distinct_keys, blevel, leaf_blocks, distinct_keys/leaf_blocks 2 from user_indexes where index_name = ‘DEMO_IBS_90_10_U1‘ 3 / NUM_ROWS DISTINCT_KEYS BLEVEL LEAF_BLOCKS DISTINCT_KEYS/LEAF_BLOCKS ---------- ------------- ---------- ----------- ------------------------- 50653 50653 1 263 192.596958 SQL> analyze index demo_ibs_90_10_u1 validate structure; Index analyzed. SQL> select lf_rows, lf_blks, br_blks, del_lf_rows, pct_used from index_stats; LF_ROWS LF_BLKS BR_BLKS DEL_LF_ROWS PCT_USED ---------- ---------- ---------- ----------- ---------- 50653 263 1 0 100
这个分析过程,首先从v$statname , v$mystat表中查询到索引分裂的数量信息,然后通过调用dbms_stats的gather_table_stats函数搜集信息,再通过user_indexes 查询展示搜集的数据信息,最后对索引的结构进行分析操作,并通过index_stats查询索引数据块的信息。
结论:
- 在插入50653数据的过程中,产生了262次索引分裂,且都是90-10分裂
- 产生了263个数据块,且每个数据块中大概有193个索引值
接下来进行第二次数据插入操作,需要重新建立一个新的会话,目的是方便分析插入的索引信息。
SQL> connect scott/tiger Connected. SQL> SQL> declare 2 i number; 3 4 begin 5 for i in 1..1000 6 loop 7 insert into demo_ibs_90_10 8 select object_id+100000+i,substr(owner,1,10),substr(object_type,1,18),rpad(object_name,20,dbms_random.string(‘X‘,3)),created+vsize(object_name) 9 from source_table 10 where object_id is not null 11 and object_id = 1000+i; 12 commit; 13 end loop; 14 end; 15 / PL/SQL procedure successfully completed.
和之前一样,分析插入数据
SQL> SQL> select sn.name, ms.value 2 from v$statname sn, v$mystat ms 3 where sn.statistic#=ms.statistic# 4 and sn.name like ‘%leaf node %‘ 5 order by 1 6 / NAME VALUE ---------------------------------------------------------------- ---------- leaf node 90-10 splits 5 leaf node splits 5 SQL> SQL> exec dbms_stats.gather_table_stats(‘‘,‘DEMO_IBS_90_10‘,estimate_percent=>100,cascade=>TRUE); PL/SQL procedure successfully completed. SQL> select num_rows, distinct_keys, blevel, leaf_blocks, distinct_keys/leaf_blocks 2 from user_indexes where index_name = ‘DEMO_IBS_90_10_U1‘ 3 / NUM_ROWS DISTINCT_KEYS BLEVEL LEAF_BLOCKS DISTINCT_KEYS/LEAF_BLOCKS ---------- ------------- ---------- ----------- ------------------------- 51653 51653 1 268 192.735075 SQL> analyze index demo_ibs_90_10_u1 validate structure; Index analyzed. SQL> select lf_rows, lf_blks, br_blks, del_lf_rows, pct_used from index_stats; LF_ROWS LF_BLKS BR_BLKS DEL_LF_ROWS PCT_USED ---------- ---------- ---------- ----------- ---------- 51653 268 1 0 100 SQL>
这里,通过新建一个过程,将数据的object_id的值增加100000再进行插入。
可以发现,第二轮插入产生了5次90-10分裂,数据块的数量也增加到了268个。(每个块大概是193个索引值,第二轮中插入了1000个值,所以是分裂了5次)
然后进行第三次的数据插入操作, 操作和上面两轮基本一致。
SQL> connect scott/tiger Connected. SQL> SQL> insert into demo_ibs_90_10 2 select object_id+200000,substr(owner,1,10),substr(object_type,1,18),rpad(object_name,20,dbms_random.string(‘X‘,3)),created+vsize(object_name) 3 from source_table 4 where object_id is not null 5 and object_id between 1000 and 2000 6 / 1001 rows created. SQL> SQL> commit; Commit complete. SQL> SQL> select sn.name, ms.value 2 from v$statname sn, v$mystat ms 3 where sn.statistic#=ms.statistic# 4 and sn.name like ‘%leaf node %‘ 5 order by 1 6 / NAME VALUE ---------------------------------------------------------------- ---------- leaf node 90-10 splits 6 leaf node splits 6 SQL> SQL> exec dbms_stats.gather_table_stats(‘‘,‘DEMO_IBS_90_10‘,estimate_percent=>100,cascade=>TRUE); PL/SQL procedure successfully completed. SQL> select num_rows, distinct_keys, blevel, leaf_blocks, distinct_keys/leaf_blocks 2 from user_indexes where index_name = ‘DEMO_IBS_90_10_U1‘ 3 / NUM_ROWS DISTINCT_KEYS BLEVEL LEAF_BLOCKS DISTINCT_KEYS/LEAF_BLOCKS ---------- ------------- ---------- ----------- ------------------------- 52654 52654 1 274 192.167883 SQL> analyze index demo_ibs_90_10_u1 validate structure; Index analyzed. SQL> select lf_rows, lf_blks, br_blks, del_lf_rows, pct_used from index_stats; LF_ROWS LF_BLKS BR_BLKS DEL_LF_ROWS PCT_USED ---------- ---------- ---------- ----------- ---------- 52654 274 1 0 100 SQL>
经过三轮的数据操作,可以发现,对一个单调递增的序列上进行新值的插入,索引分裂是90-10分裂类型的。
思考:
如果在插入的过程中有删除的操作,会发生什么样的情况呢?
实际上,oracle并不总是把新值直接插入到任意的一个数据块中的,即是该数据块当前是90%空闲的。当原有的叶子节点上的旧数据被删除的时候,这个数据块就可以被重复利用,作为当前“最右”的数据块的下一个块(即是说,数据块和索引值并不是物理上的连续的排序,只是逻辑上的往右排序下去)。