技术开发 频道

事务队列等待深入分析:ITL争用

  死锁分析

  当死锁事务中存在ITL等待时,我们可以按照以下思路进行分析。之前说过,ITL等待不是一个经常出现的事件,偶尔的导致的死锁问题我们或许可以忽略。但是如果经常出现由此导致的死锁就应该引起注意了。判断死锁等待是否涉及ITL等待,可以通过从Trace文件中找到关联会话的详细Trace部分,可以发现以下等待事件信息: 

last wait for 'enq: TX - allocate ITL entry' blocking sess=0x1EDDD0DC seq=74 wait_time=2999976 seconds since wait started=111

  name
|mode=54580004, usn<<16 | slot=90022, sequence=22c95

  Dumping Session Wait History

  
for 'enq: TX - allocate ITL entry' count=1 wait_time=2999976

  name
|mode=54580004, usn<<16 | slot=90022, sequence=22c95

  然而,和v$session中ROW_WAIT_*字段一样,如果死锁中存在共享锁,那么trace文件中Rows waited on部分的信息可能并不正确。我们需要一个更加详细的deadlock trace来做深入分析。 

HELLODBA.COM> alter system set events '60 trace name ERRORSTACK level 3; name systemstate level 266';

  System altered.

  以下的代码模拟了Index的Maxtrans限制而导致死锁(为了减少事务量,我将模拟ITL等待的索引建立在块大小为2k的表空间上): 

--建立测试表和SP

  HELLODBA.COM
> create table tx_test_itl (a number, b varchar2(800), c date) logging pctfree 10;

  
Table created.

  
-- 我们需要在这个索引上造成ITL等待,为了减少事务量,将其建立在数据块为2k的表空间上

  HELLODBA.COM
> create index tx_test_itl_idx1 on tx_test_itl (a) tablespace idx_2k pctfree 10;

  
Index created.

  HELLODBA.COM
> create index tx_test_itl_idx2 on tx_test_itl (c) tablespace ringidx pctfree 10;

  
Index created.

  HELLODBA.COM
> begin

  
2 for i in 1..5300

  
3 loop

  
4 insert into tx_test_itl (a, b, c) values (i, dbms_random.string(1,trunc(dbms_random.value()*10)), sysdate);

  
5 end loop;

  
6 delete from tx_test_itl where (trunc(a/4) = a/4 or trunc(a/9) = a/9);

  
7 end;

  
8 /

  PL
/SQL procedure successfully completed.

  HELLODBA.COM
> commit;

  
Commit complete.

  HELLODBA.COM
> select count(*) from tx_test_itl;

  
COUNT(*)

  
----------

  
3534

  HELLODBA.COM
> create or replace procedure recruit_insert( p_cnt in number, p_str in varchar2, p_max in number)

  
2 as

  
3 pragma autonomous_transaction;

  
4 begin

  
5 if (p_cnt > p_max)

  
6 then

  
7 return;

  
8 end if;

  
9 update t_test1 set created=sysdate, subobject_name='AAA' where object_id = trunc(dbms_random.value()*10000);

  
10 --update t_test1 set subobject_name='AAA' where object_id = p_cnt;

  
11 insert into tx_test_itl values (p_cnt, p_str, sysdate);

  
12 recruit_insert(p_cnt+1, p_str, p_max);

  
13 if (p_cnt = p_max) then

  
14 sys.dbms_lock.sleep(60);

  
15 end if;

  
16 update t_test5 set username='AAA' where user_id=1;

  
17 rollback;

  
18 end;

  
19 /

  
Procedure created.

  
--会话1中执行

  HELLODBA.COM
> update t_test5 set username='AAA' where user_id=1;

  
1 row updated.

  
--会话2中执行。为了达到所有40个事务都在同一个数据块上的效果,tx_test_itl_idx1的最后一个数据块必须足够小,以容纳新插入的40条数据和ITL slot。

  HELLODBA.COM
> exec recruit_insert( 5300+1, 'A', 5300+40 );

  
--会话1中执行,造成ITL等待,由于其已经对T_TEST5的一条数据进行UPDATE造成会话2的请求等待,因此形成死锁

  HELLODBA.COM
> insert into tx_test_itl(a, b, c) values (5360, 'A', sysdate);

  
insert into tx_test_itl(a, b, c) values (5360, 'A', sysdate)

  
*

  ERROR at line
1:

  ORA
-00060: deadlock detected while waiting for resource

  然后,我们对生成的TRACE文件进行深入分析。

  首先看到死锁链:

 Deadlock graph:

  
---------Blocker(s)-------- ---------Waiter(s)---------

  Resource Name process session holds waits process session holds waits

  TX
-00180012-0000027b 21 311 X 22 295 X

  TX
-005e0017-00000051 22 295 X 21 311 S

  被阻塞会话是311,发生死锁时正在运行的语句是: 

 *** SESSION ID:(311.277) 2009-09-22 09:28:38.322

  DEADLOCK DETECTED

  
[Transaction Deadlock]

  
Current SQL statement for this session:

  
insert into tx_test_itl(a, b, c) values (5360, 'A', sysdate)

  找到被阻塞的语句对于我们确定发生ITL等待的对象很重要。

  阻塞会话是295,找到其事务地址:

 (session) sid: 295 trans: 1C7B729C, creator: 1F7CE4F8, flag: (100041) USR/- BSY/-/-/-/-/-

  DID:
0001-0016-00000039, short-term DID: 0000-0000-00000000

  txn branch:
00000000

  oct:
6, prv: 0, sql: 1AD1C424, psql: 1F18B8DC, user: 35/DEMO

  其事务地址为1C7B729C,然后由此地址在Trace文件中找到这个事务下面的队列信息,其中TX队列(type: 39)是我们感兴趣的: 

 SO: 1DECF7B0, type: 39, owner: 1C7B729C, flag: -/-/-/0x00

  (List
of Blocks) next index = 6

  
index itli buffer hint rdba savepoint

  
-----------------------------------------------------------

  
0 3 0x15bf86fc 0x200d9b2 0x443

  
1 4 0x133fa53c 0x141087e 0x445

  
2 38 0x10fef32c 0x1401f5f 0x446

  
3 41 0x1701576c 0x1401e0d 0x44b

  
4 2 0x107ddeec 0x3c00199 0x44e

  
5 41 0x170100bc 0x2402b88 0x450

  可以看到,这里列出了事务所作用到的所有数据块。结合之前找到的被阻塞的语句,我们知道等待是发生在tx_test_itl或者其索引上。我们看下这些数据块分别是属于哪些对象: 

HELLODBA.COM> select owner, segment_name from dba_extents

  
2 where file_id = dbms_utility.data_block_address_file(TO_NUMBER('200d9b2', 'XXXXXXXX'))

  
3 and dbms_utility.data_block_address_block(TO_NUMBER('200d9b2', 'XXXXXXXX')) between block_id and block_id+blocks;

  OWNER SEGMENT_NAME

  
---------------- -------------------

  DEMO T_TEST1

  ... ...

  最终得知0x1401e0d是表tx_test_itl的数据块,0x3c00199是索引tx_test_itl_idx1的数据块,0x2402b88是索引tx_test_itl_idx2的数据块,这3个数据块是我们感兴趣的块。我们之前说过,数据块上的ITL slot被分配就不会被回收了,而索引数据块如果发生分裂也会继承原有数据块上ITL slot。因此我们可以通过将这3个数据块dump出来判断是哪个数据块上发生的ITL等待(按照先索引再表的顺序,因为索引只有达到最大限制和分裂事务ITL slot争用时才发生ITL等待,如果索引块这两个条件都没有满足,可以判断是表的数据块发生ITL等待)。最终,我们会发现是0x3c00199上达到ITL slot上限(2k数据块,上限为41):

 Object id on Block? Y

  seg
/obj: 0x3078e csc: 0x00.b0e1e2b9 itc: 41 flg: E typ: 2 - INDEX

  brn:
0 bdba: 0x3c00182 ver: 0x01 opc: 0

  inc:
0 exflg: 0

  Itl Xid Uba Flag Lck Scn
/Fsc

  
0x01 0x0008.02b.00020f75 0x00801eeb.76b3.09 CB-- 0 scn 0x0000.b0e11508

  
0x02 0x0035.027.00000061 0x0080033e.0056.03 C--- 0 scn 0x0000.b0e1ddd8

  ... ...

  
0x29 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000

  此外,还有一个思路:通过被阻塞事务的队列信息找到其TX队列中的加锁数据块:

SO: 1DED2DB0, type: 39, owner: 1DF1E5F4, flag: -/-/-/0x00

  (List
of Blocks) next index = 2

  
index itli buffer hint rdba savepoint

  
-----------------------------------------------------------

  
0 2 0x133eca9c 0x14103d7 0x114

  
1 2 0x137ee82c 0x1401e12 0x11c

  可以看到,这2个数据块分别属于表T_TEST5和tx_test_itl,而被阻塞的语句是对tx_test_itl插入一条数据,而此时表中已经插入数据并产生了UNDO数据,说明表tx_test_itl上并没有发生等待,再比较阻塞事务中的数据块信息,可以判断ITL等待是发生在索引上面。

  解决方法

  系统中存在少量的ITL等待是正常的,只有当其对系统造成了影响(如awr report中,在top 5 events中发现该事件),或者对应用造成了直接影响(如死锁,再如发现某一会话存在大量等待ITL),我们才需要采取相应手段进行处理。针对导致ITL等待不同原因,我们要采取不同的手段来处理。

  INITRANS不足

  这种情况只会出现的表的数据块上,如我们上述的例子:数据块上的ITL数量并没有达到MAX TRANS的限制,可用空间小于24字节。发生这种情况的表通常会被经常UPDATE,从而造成预留空间(PCTFREE)被填满。如果我们发现这类ITL等待对系统已经造成影响,可以通过增加表的INITRANS或者PCTFREE来解决(视该表上的并发事务量而定,通常,如果并发量高,建议优先增加INITRANS,反之,则优先考虑增加PCTFREE)。

  要注意的一点是,如果是使用ALTER TABLE的方式修改这2个参数的话,只会影响新的数据块,而不会改变已有数据的数据块——要做的这一点,需要将数据导出/导入、重建表。

  MAXTRANS不足

  这一情况是由高并发引起的:同一数据块上的事务量已经超出了其实际允许的ITL数(如前所述,ITL slot所占空间不能超过数据块大小的一半,如8K的限制为169)。因此,要解决这类问题就需要从应用着手,减少事务的并发量;长事务,在保证数据完整性的前提下,增加commit的频率,修改为短事务,减少资源占用事件。而对于OLAP系统来说(例如,其存在高并发量的数据录入模块),可以考虑增大数据块大小。

  递归事务ITL争用

  这一类等待通常是系统存在并发事务频繁插入、修改数据导致,其往往伴随"enq: TX - index contention"事件出现。根本解决方法就是要减少索引分裂,如使用大数据块、减少索引中效率低、使用率低的字段等。

0
相关文章