我们知道Oracle的最小的数据存储单位是块(block),ITL事务槽就是位于块头的一部分,它的作用是记录该数据块所发生的事务,一个被使用的ITL事务槽对应当前数据块的一条事务,ITL事务槽的初始数量由initrans参数控制(最小值为1,但实际默认分配两个ITL事务槽),最大数量由maxtrans参数控制(Oracle 10g及以上版本此参数固定为255,不可变更),当事务提交时事务槽可以被重用,我们可以通过dump一个数据块来观察它的结构:
创建测试表插入数据

SQL> create table t1(id number,text varchar2(200));
Table created.
SQL> insert into t1 values(1,'example');
1 row created.
SQL> commit;
Commit complete.
SQL> alter system checkpoint;
System altered.

查看当前表使用的块编号,dump这个数据块

SQL> select id, dbms_rowid.rowid_relative_fno(rowid) file#,dbms_rowid.rowid_block_number(rowid) block# from t1;
	ID	FILE#	  BLOCK#
---------- ---------- ----------
	 1	    7	   22965
SQL> alter system dump datafile 7 block 22965;
System altered.

查询生成的trc文件

select i.instance_name||'_ora_' || p.spid|| '.trc' TRACE_NAME from v$session s,v$process p ,v$instance i where s.paddr=p.addr and s.sid in (select userenv('sid') from dual);
TRACE_NAME
--------------------------------------------------------------------------------
orcl_ora_1784.trc

此时可以在数据块的头部发现ITL事务槽的结构如下

Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0003.01f.00000567  0x010016ae.0180.01  --U-    1  fsc 0x0000.002f470e
0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

可以发现不添加任何参数建表时,其ITL事务槽默认有两个,也就是不论initrans参数是1或者是2,一个BLOCK的事务槽数量至少为2。观察其构成,XID对应回滚段中对应的事务xid,Uba对应回滚段地址,flag对应当前的事务状态,以及最后的事务SCN。

那什么原因会导致enq: TX - allocate ITL entry等待事件呢?

INST_ID USERNAME         SID    SERIAL#  BLOCKING_INSTANCE BLOCKING_SESSION SQL_ID                     EVENT                   		H
---------- ---------- ---------- ---------- ----------------- ---------------- -------------------------- ------------------------------ ----------
     1 		SYS        278      14642                       		3gbaumsjw3zjg             SQL*Net message to client           0
     1 		TEST       48       17664            1          45 		4vugq1rx8zthc             enq: TX - allocate ITL entry        .04

对于ITL事务槽来说,除去其初始分配的事务槽(可以理解为固定存在的事务槽),在一个BLOCK写满的情况下,继续扩展ITL事务槽需要占用PCTFREE参数指定的预留空间。例如PCTFREE值设定为10%,而一个块的大小是8KB也就是8192B,则预留的空间约为819.2B,而一个ITL事务槽的大小为24B(ITL事务槽大小参考文献,最开始查看一些文章有说大小是46B的,但是实际测试不相符),由此我们可以大致推断其最多可扩展的ITL事务槽数量。而在实际生产环境中,update语句更新该数据块中的字段,使其字段长度增加,也会占用PCTFREE预留空间,导致ITL事务槽的可扩展数量减少。

从根本上来说enq: TX - allocate ITL entry等待事件的产生是由于当前BLOCK没有足够的空间去扩展事务槽,或者由于多个长事务导致的ITL事务槽长时间占用,从而引起事务槽争用,那么解决方法也十分明了:

  1. 修改PCTFREE值预留更多的空间
  2. 修改initrans参数预置一定数量的事务槽
  3. 修改业务语句,减少长事务

需要注意的是1.2两种方法若是在已创建完成的表中,只会对修改参数后新生成的BLOCK有效,若想更改之前的BLOCK需要MOVE TABLE等操作。

增大相关表的PCT_FREE及INITRANS,如:

alter table TX_SEND_TEMP PCTFREE 20 INITRANS 40;


问题原因

产生enq: TX - allocate ITL entry等待事件需要修改分区表initrans值

解决方案

1.修改表的INITRANS值

alter table owner1.table1 INITRANS 20; --分区表直接改表不需要每个分区都改

2.批量move表空间使得历史partition block INITRANS生效

begin
for x in(select partition_name from dba_tab_partitions where table_name='table1' and TABLE_OWNER='owner1')
loop
execute immediate 'alter table owner1.table1 move partition ' || x.partition_name || ' tablespace tablespace1';
end loop;
end;
/

3.查询重建失效索引(表的INITRANS值改了,索引也建议相应更改 alter index index_name initrans 20;)

SELECT 'alter index  '||owner||'.'||index_name|| ' rebuild online;' from dba_indexes where status='UNUSABLE'; --全局索引
SELECT 'alter index  '||index_owner||'.'||index_name|| ' rebuild partition '||partition_name || ';' from dba_ind_partitions  where status='UNUSABLE'; --分区索引

4.注意Move表空间会生成归档,要及时删除归档,同时增加INITRANS会增加表空间使用要及时维护表空间,如遇失效存储过程要重新编译

5.统计表信息

execute dbms_stats.gather_table_stats(ownname => 'owner1',tabname => 'table1' ,estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,method_opt => 'for all  columns size auto' ,cascade => true ,degree=>6);