[English]
作者:
fuyuncat
来源:
www.HelloDBA.com
尽管索引分裂是由递归事务控制的,其资源的请求与释放都很短暂,不受用户事务是否结束的影响,但是,在并发环境中,索引分裂仍然会导致一些等待事件。
enq: TX - Index contention
首先一个与索引分裂直接相关的等待事件,也是仅仅因为索引分裂才会导致的等待事件是"enq: TX - Index contention"。这一等待是一种TX队列等待,在10g之前,被笼统的归入TX队列等待中,10g之后,才有了更细致的划分。
当一个更新事务需要插入/删除某个索引块上的数据,而这个数据块正在被另外一个事务分裂,则需要等待分裂完成后才能修改上面的数据,此时就会发生“enq: TX - Index contention”等待事件:
SQL代码
- HELLODBA.COM> create table tx_index_contention (a number, b varchar2(1446), c date);
- Table created.
- HELLODBA.COM> create index tx_index_contention_idx1 on tx_index_contention (c, b) tablespace idx_2k pctfree 10;
- Index created.
- --session 1,产生大量的索引块分裂:
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> select distinct sid from v$mystat;
- SID
- ----------
- 320
- HELLODBA.COM> begin
- 2 for i in 1..2000
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
- --session 2, 在索引分裂的同时,插入数据:
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> select distinct sid from v$mystat;
- SID
- ----------
- 307
- HELLODBA.COM> begin
- 2 for i in 1..1000
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 20, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
- PL/SQL procedure successfully completed.
可以看到,第二个会话中出现了"enq: TX - Index contention"等待:
SQL代码
- HELLODBA.COM> select sid, event, total_waits from v$session_event where sid=307 and event = 'enq: TX - indexcontention';
- SID EVENT TOTAL_WAITS
- ---------- ------------------------------- ----------------
- 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代码
- HELLODBA.COM> truncate table tx_index_contention;
- Table truncated.
- --Session 1, 发生大量索引块分裂
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> select distinct sid from v$mystat;
- SID
- ----------
- 312
- HELLODBA.COM> begin
- 2 for i in 1..2000
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
- -- Session 2 中同时发生分裂
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> select distinct sid from v$mystat;
- SID
- ----------
- 307
- HELLODBA.COM> begin
- 2 for i in 1..2000
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
可以看到两个会话中都发生了“enq: TX - allocate ITL entry”等待:
SQL代码
- HELLODBA.COM> select sid, event, total_waits from v$session_event where sid in (312,307) and event = 'enq: TX - allocate ITL entry';
- SID EVENT TOTAL_WAITS
- ---------- ------------------------------ -----------------
- 307 enq: TX - allocate ITL entry 10
- 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代码
- HELLODBA.COM> conn demo/demo
- Connected.
- --事务未被提交,内存块被释放
- HELLODBA.COM> begin
- 2 for i in 1..100
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
- PL/SQL procedure successfully completed.
- HELLODBA.COM> alter system flush buffer_cache;
- System altered.
此时(在另外会话中)读取索引,发生db file sequential read等待
SQL代码
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> set autot trace stat
- HELLODBA.COM> select /*+index(t tx_index_contention_idx1)*/* from tx_index_contention t where c<sysdate;
- no rows selected
- Statistics
- ----------------------------------------------------------
- 9 recursive calls
- 0 db block gets
- 756 consistent gets
- 147 physical reads
- 14648 redo size
- 372 bytes sent via SQL*Net to client
- 374 bytes received via SQL*Net from client
- 1 SQL*Net roundtrips to/from client
- 0 sorts (memory)
- 0 sorts (disk)
- 0 rows processed
- HELLODBA.COM> set autot off
- HELLODBA.COM> select sid, event, total_waits from v$session_event where sid in (307) and event = 'db file sequential read';
- SID EVENT TOTAL_WAITS
- ---------- -------------------------- -------------
- 307 db file sequential read 133
这种情况下db file sequential read等待和并发的索引块分裂是无关的——是因为分裂导致索引段的数据块增加。但下面这种情况,就和并发索引分裂相关。
前面提过,当数据块上当大量数据被删除,或者插入数据的事务被回滚,会在索引结构中留下大量空数据块被放入freelist,此时发生索引分裂,将可能会引起更多“db file sequential read”等待:当一个事务进行分裂时,从freelist的前列读取到空闲数据块——该数据块是由其它事务删除数据或者回滚而被放入freelist的,而如果此时该空闲数据块状态异常(删除事务未提交、或者有新的数据被重新插入该数据块),则分裂事务需要再重新读取空闲数据块。比较下面2段代码:
1、索引构建好后,从buffer中置换出:
SQL代码
- HELLODBA.COM> truncate table tx_index_contention;
- Table truncated.
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> begin
- 2 for i in 1..100
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
- PL/SQL procedure successfully completed.
- HELLODBA.COM> commit;
- Commit complete.
- HELLODBA.COM> alter system flush buffer_cache;
- System altered.
此时另外一个事务分裂索引会导致60的db file sequential read等待:
SQL代码
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> begin
- 2 for i in 1..100
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
- PL/SQL procedure successfully completed.
- 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';
- SID EVENT TOTAL_WAITS
- ---------- -------------------------- ------------
- 307 db file sequential read 60
2、而如果事务将构建好的索引数据删除,相应数据块被放到freelist中去了,此时事务未提交,这些数据块的状态不适合作为分裂时的新数据块:
SQL代码
- HELLODBA.COM> truncate table tx_index_contention;
- Table truncated.
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> begin
- 2 for i in 1..100
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
- PL/SQL procedure successfully completed.
- HELLODBA.COM> commit;
- Commit complete.
- HELLODBA.COM> delete from tx_index_contention;
- 100 rows deleted.
- HELLODBA.COM> alter system flush buffer_cache;
- System altered.
另外一个事务需要进行分裂时,会先读取到freelist上的数据——发现不能被作为新数据块,需重新读取空闲数据块,造成db file sequential read等待增加:
SQL代码
- HELLODBA.COM> conn demo/demo
- Connected.
- HELLODBA.COM> begin
- 2 for i in 1..100
- 3 loop
- 4 insert into tx_index_contention (a, b, c) values (i, lpad('A', 1000, 'A'), sysdate);
- 5 end loop;
- 6 end;
- 7 /
- PL/SQL procedure successfully completed.
- 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';
- SID EVENT TOTAL_WAITS
- ---------- -------------------------- -----------------
- 307 db file sequential read 175
这种情况下,还会可能导致连锁等待:分裂事务会对被分裂数据块加共享锁,此时如果有其它事务需要向该数据块写入数据,那么这些事务就会进入等待队列,并记录"enq: TX - index contention",直到分裂事务找到可用数据块、完成分裂。
--- Fuyuncat - The End ---