HelloDBA [English]
搜索Internet 搜索 HelloDBABA
  Oracle技术站。email: fuyuncat@gmail.com  MSN: fuyuncat@hotmail.com   acoug  acoug 

B*Tree 索引中的数据块分裂——等待事件

[English]

作者: fuyuncat

来源: www.HelloDBA.com

日期: 2009-10-19 07:25:54

分享到  新浪微博 腾讯微博 人人网 i贴吧 开心网 豆瓣 淘宝 推特 Facebook GMail Blogger Orkut Google Bookmarks

    尽管索引分裂是由递归事务控制的,其资源的请求与释放都很短暂,不受用户事务是否结束的影响,但是,在并发环境中,索引分裂仍然会导致一些等待事件。

enq: TX - Index contention

    首先一个与索引分裂直接相关的等待事件,也是仅仅因为索引分裂才会导致的等待事件是"enq: TX - Index contention"。这一等待是一种TX队列等待,在10g之前,被笼统的归入TX队列等待中,10g之后,才有了更细致的划分。

    当一个更新事务需要插入/删除某个索引块上的数据,而这个数据块正在被另外一个事务分裂,则需要等待分裂完成后才能修改上面的数据,此时就会发生“enq: TX - Index contention”等待事件:

SQL代码
  1. HELLODBA.COM> create table tx_index_contention (a number, b varchar2(1446), c date);   
  2.   
  3. Table created.   
  4.   
  5. HELLODBA.COM> create index tx_index_contention_idx1 on tx_index_contention (c, b) tablespace idx_2k pctfree 10;   
  6.   
  7. Index created.   
  8.   
  9. --session 1,产生大量的索引块分裂:   
  10. HELLODBA.COM> conn demo/demo   
  11. Connected.   
  12. HELLODBA.COM> select distinct sid from v$mystat;   
  13.   
  14.        SID   
  15. ----------   
  16.        320   
  17.   
  18. HELLODBA.COM> begin  
  19.   2     for i in 1..2000   
  20.   3     loop   
  21.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);   
  22.   5     end loop;   
  23.   6  end;   
  24.   7  /   
  25.   
  26. --session 2, 在索引分裂的同时,插入数据:   
  27. HELLODBA.COM> conn demo/demo   
  28. Connected.   
  29. HELLODBA.COM> select distinct sid from v$mystat;   
  30.   
  31.        SID   
  32. ----------   
  33.        307   
  34.   
  35. HELLODBA.COM> begin  
  36.   2     for i in 1..1000   
  37.   3     loop   
  38.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 20, 'A'), sysdate);   
  39.   5     end loop;   
  40.   6  end;   
  41.   7  /   
  42.   
  43. PL/SQL procedure successfully completed.  

    可以看到,第二个会话中出现了"enq: TX - Index contention"等待:

SQL代码
  1. HELLODBA.COM> select sid, event, total_waits from v$session_event where sid=307 and event = 'enq: TX - indexcontention';   
  2.   
  3.        SID EVENT                           TOTAL_WAITS   
  4. ---------- ------------------------------- ----------------   
  5.        307 enq: TX - index contention      8  

enq: TX - allocate ITL entry

    这一等待也是属于TX队列等待。

    在索引数据块上,有2种情形会导致发生“enq: TX - allocate ITL entry”等待:1、达到数据块上最大事务数限制;2、递归事务ITL争用。很显然,第二种情形是由索引分裂引起的:当一个事务中递归事务请求分裂一个数据块时,该数据块正在被另外一个事务的递归事务分裂,就发生“enq: TX - allocate ITL entry”等待。我们前面提过,无论在叶子节点数据块上还是在枝节点数据块上,有且只有一个ITL slot(枝节点上的唯一ITL slot,叶子节点上的第一条ITL slot)是用于递归事务的,当2个递归事务同时要请求该ITL slot,后发出请求的事务就需要等待:

SQL代码
  1. HELLODBA.COM> truncate table tx_index_contention;   
  2.   
  3. Table truncated.   
  4.   
  5. --Session 1, 发生大量索引块分裂   
  6. HELLODBA.COM> conn demo/demo   
  7. Connected.   
  8. HELLODBA.COM> select distinct sid from v$mystat;   
  9.   
  10.        SID   
  11. ----------   
  12.        312   
  13.   
  14. HELLODBA.COM> begin  
  15.   2     for i in 1..2000   
  16.   3     loop   
  17.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);   
  18.   5     end loop;   
  19.   6  end;   
  20.   7  /   
  21.   
  22. -- Session 2 中同时发生分裂   
  23. HELLODBA.COM> conn demo/demo   
  24. Connected.   
  25. HELLODBA.COM> select distinct sid from v$mystat;   
  26.   
  27.        SID   
  28. ----------   
  29.        307   
  30.   
  31. HELLODBA.COM> begin  
  32.   2     for i in 1..2000   
  33.   3     loop   
  34.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);   
  35.   5     end loop;   
  36.   6  end;   
  37.   7  /  

    可以看到两个会话中都发生了“enq: TX - allocate ITL entry”等待:

SQL代码
  1. HELLODBA.COM> select sid, event, total_waits from v$session_event where sid in (312,307) and event = 'enq: TX - allocate ITL entry';   
  2.   
  3.        SID EVENT                          TOTAL_WAITS   
  4. ---------- ------------------------------ -----------------   
  5.        307 enq: TX - allocate ITL entry   10   
  6.        312 enq: TX - allocate ITL entry   8  

db file sequential read

    “db file sequential read”是因为Oracle从磁盘上读取单个数据块到内存中发生的等待——索引的读取就是单个数据块的读取(Fast Full Index Scan除外)。

    当发生索引块分裂,新数据块立即被加入索引树结构(和事务是否结束无关),这些新数据块被放入LRU链表中,Touch Count为1——因此容易被从buffer中置换出。此时,如果发生对索引的读,这些新数据块也会被读取——如果此时它们已经不在内存中,则会导致“db file sequential read”等待的增加:

SQL代码
  1. HELLODBA.COM> conn demo/demo   
  2. Connected.   
  3.   
  4. --事务未被提交,内存块被释放   
  5. HELLODBA.COM> begin  
  6.   2     for i in 1..100   
  7.   3     loop   
  8.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);   
  9.   5     end loop;   
  10.   6  end;   
  11.   7  /   
  12.   
  13. PL/SQL procedure successfully completed.   
  14.   
  15. HELLODBA.COM> alter system flush buffer_cache;   
  16.   
  17. System altered.  

    此时(在另外会话中)读取索引,发生db file sequential read等待

SQL代码
  1. HELLODBA.COM> conn demo/demo   
  2. Connected.   
  3. HELLODBA.COM> set autot trace stat   
  4. HELLODBA.COM> select /*+index(t tx_index_contention_idx1)*/* from tx_index_contention t where c<sysdate;   
  5.   
  6. no rows selected   
  7.   
  8. Statistics  
  9. ----------------------------------------------------------   
  10.           9  recursive calls   
  11.           0  db block gets   
  12.         756  consistent gets   
  13.         147  physical reads   
  14.       14648  redo size  
  15.         372  bytes sent via SQL*Net to client   
  16.         374  bytes received via SQL*Net from client   
  17.           1  SQL*Net roundtrips to/from client   
  18.           0  sorts (memory)   
  19.           0  sorts (disk)   
  20.           0  rows processed   
  21.   
  22. HELLODBA.COM> set autot off  
  23. HELLODBA.COM> select sid, event, total_waits from v$session_event where sid in (307) and event = 'db file sequential read';   
  24.   
  25.        SID EVENT                      TOTAL_WAITS   
  26. ---------- -------------------------- -------------   
  27.        307 db file sequential read    133  

    这种情况下db file sequential read等待和并发的索引块分裂是无关的——是因为分裂导致索引段的数据块增加。但下面这种情况,就和并发索引分裂相关。

    前面提过,当数据块上当大量数据被删除,或者插入数据的事务被回滚,会在索引结构中留下大量空数据块被放入freelist,此时发生索引分裂,将可能会引起更多“db file sequential read”等待:当一个事务进行分裂时,从freelist的前列读取到空闲数据块——该数据块是由其它事务删除数据或者回滚而被放入freelist的,而如果此时该空闲数据块状态异常(删除事务未提交、或者有新的数据被重新插入该数据块),则分裂事务需要再重新读取空闲数据块。比较下面2段代码:

1、索引构建好后,从buffer中置换出:
 

SQL代码
  1. HELLODBA.COM> truncate table tx_index_contention;   
  2.   
  3. Table truncated.   
  4.   
  5. HELLODBA.COM> conn demo/demo   
  6. Connected.   
  7. HELLODBA.COM> begin  
  8.   2     for i in 1..100   
  9.   3     loop   
  10.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);   
  11.   5     end loop;   
  12.   6  end;   
  13.   7  /   
  14.   
  15. PL/SQL procedure successfully completed.   
  16.   
  17. HELLODBA.COM> commit;   
  18.   
  19. Commit complete.   
  20.   
  21. HELLODBA.COM> alter system flush buffer_cache;   
  22.   
  23. System altered.  

    此时另外一个事务分裂索引会导致60的db file sequential read等待:

SQL代码
  1. HELLODBA.COM> conn demo/demo   
  2. Connected.   
  3. HELLODBA.COM> begin  
  4.   2     for i in 1..100   
  5.   3     loop   
  6.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);   
  7.   5     end loop;   
  8.   6  end;   
  9.   7  /   
  10.   
  11. PL/SQL procedure successfully completed.   
  12.   
  13. HELLODBA.COM> select sid, event, total_waits from v$session_event where sid in (select sid from v$mystat) and event = 'db file sequential read';   
  14.   
  15.        SID EVENT                      TOTAL_WAITS   
  16. ---------- -------------------------- ------------   
  17.        307 db file sequential read    60  

    2、而如果事务将构建好的索引数据删除,相应数据块被放到freelist中去了,此时事务未提交,这些数据块的状态不适合作为分裂时的新数据块:

SQL代码
  1. HELLODBA.COM> truncate table tx_index_contention;   
  2.   
  3. Table truncated.   
  4.   
  5. HELLODBA.COM> conn demo/demo   
  6. Connected.   
  7. HELLODBA.COM> begin  
  8.   2     for i in 1..100   
  9.   3     loop   
  10.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);   
  11.   5     end loop;   
  12.   6  end;   
  13.   7  /   
  14.   
  15. PL/SQL procedure successfully completed.   
  16.   
  17. HELLODBA.COM> commit;   
  18.   
  19. Commit complete.   
  20.   
  21. HELLODBA.COM> delete from tx_index_contention;   
  22.   
  23. 100 rows deleted.   
  24.   
  25. HELLODBA.COM> alter system flush buffer_cache;   
  26.   
  27. System altered.  

    另外一个事务需要进行分裂时,会先读取到freelist上的数据——发现不能被作为新数据块,需重新读取空闲数据块,造成db file sequential read等待增加:

SQL代码
  1. HELLODBA.COM> conn demo/demo   
  2. Connected.   
  3. HELLODBA.COM> begin  
  4.   2     for i in 1..100   
  5.   3     loop   
  6.   4         insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);   
  7.   5     end loop;   
  8.   6  end;   
  9.   7  /   
  10.   
  11. PL/SQL procedure successfully completed.   
  12.   
  13. HELLODBA.COM> select sid, event, total_waits from v$session_event where sid in (select sid from v$mystat) and event in = 'db file sequential read';   
  14.   
  15.        SID EVENT                      TOTAL_WAITS   
  16. ---------- -------------------------- -----------------   
  17.        307 db file sequential read    175  

    这种情况下,还会可能导致连锁等待:分裂事务会对被分裂数据块加共享锁,此时如果有其它事务需要向该数据块写入数据,那么这些事务就会进入等待队列,并记录"enq: TX - index contention",直到分裂事务找到可用数据块、完成分裂。

    --- Fuyuncat - The End ---

Top

Copyright ©2005,HelloDBA.Com 保留一切权利

申明
by fuyuncat