
好的,这是一个极其核心的Oracle存储层话题。理解数据块的内部结构,尤其是ITL机制,是诊断和解决高并发性能问题的关键。我们将深入探讨。
第一部分:官方严谨的详细阐述
一、 数据块(Block)的内部结构
Oracle的数据块(通常为8KB)并非简单的“数据容器”,而是一个高度结构化的微型宇宙。其结构主要分为三个部分:
-
块头(Header):
- 块头地址(Block Address): 包含数据文件号(AFN)和块号(BLOCK#),用于唯一标识此块。
- 表目录(Table Directory): 对于堆组织表(Heap-Organized Table),此区域说明该块中存储的数据属于哪个表。
- 行目录(Row Directory): 包含一个指针数组,指向块中每行数据(Row Piece)的实际起始位置。即使行被删除,此目录项也可能暂时存在,直到空间被重用。
- 事务槽(ITL - Interested Transaction List): 这是块头的核心,也是并发控制的基石。 它是一个 slots 的数组,每个 slot 记录了一个正在或曾经修改此块的事务信息。
-
行数据空间(Row Data Space):
- 实际存储表行或索引条目(Index Entry)的区域。行数据以行片段(Row Piece) 的形式存储,可能因为行迁移/行链接而跨多个块。
-
空闲空间(Free Space):
- 可用于新行的插入或现有行的更新。
二、 ITL(Interested Transaction List)槽的深入解析
ITL是Oracle实现多版本读一致性(Read Consistency) 和行级锁定(Row-Level Locking) 的核心机制。
-
ITL槽的内容: 每个ITL槽(约24字节)包含以下关键信息:
- XID(Transaction ID): 唯一的事务标识符。
- UBA(Undo Block Address): 指向回滚段(Undo Segment)中存储该事务前映像(Before Image)的地址。这是实现读一致性的关键。
- SCN(System Change Number): 事务提交时填入的SCN。在提交前,此值为空或为锁状态。
- Flag(状态标志): 表明该事务槽的状态,例如:
ACTIVE: 事务正在进行中,尚未提交或回滚。COMMITTED: 事务已提交。DEAD: 事务已回滚或已终止(在恢复过程中标记)。
-
ITL的初始数量与动态扩展:
INITRANS: 在块被格式化(格式化通常发生在插入数据时)时,预先分配的ITL槽数量。默认值为2(索引为1)。这个空间是从块的头部分配的。MAXTRANS: 在旧版本中,它指定了一个块所能拥有的最大ITL槽数。但在现代Oracle版本(10g及以后)中,此参数已被弃用,最大限制实际上由块的可用空间决定。- 动态扩展: 当一个事务需要修改一个块,但所有现有的ITL槽都被活动事务占用时,Oracle会尝试动态扩展ITL。这意味着它会在块头部开辟一个新的ITL槽。这个操作需要消耗块头部的空闲空间。
-
ITL等待(ITL Waits)的成因与影响:
- 场景: 一个高并发表(如订单表),多个会话同时频繁地修改同一个数据块中的不同行。
- 成因:
- 初始的
INITRANS设置过小(例如为1)。 - 块头部的空闲空间不足,无法动态分配新的ITL槽。
- 初始的
- 发生过程: 当一个新事务想要修改这个块时,它需要找到一个可用的ITL槽。它会扫描ITL列表:
- 如果找到一个状态为
COMMITTED或DEAD的槽,它可以重用这个槽。 - 如果所有槽都是
ACTIVE的,并且没有空间创建新槽,则该事务必须等待,直到某个活动事务提交或回滚,释放出一个ITL槽。
- 如果找到一个状态为
- 等待事件:
enq: TX - allocate ITL entry。这是一个队列(Enqueue)等待,表明会话正在排队等待分配ITL资源。 - 具体影响: 应用程序出现阻塞,性能急剧下降。虽然这是行级锁之外的机制,但表现类似锁争用。
-
ITL与块级检查点(Block Checkpoint) SCN的关系
- 每个数据块头都有一个SCN,称为块SCN或检查点SCN。它记录了最近一次DBWR进程将此块脏写(Dirty Write) 到数据文件时的SCN。
- 在事务提交时,Oracle并不会立即将修改后的块写回磁盘。它只是在ITL槽中标记事务为
COMMITTED并填入提交SCN。 - 当发生检查点(Checkpoint)时,DBWR会将一批脏块(包含已提交和未提交的事务)写入数据文件。
- 关键点: 对于包含已提交事务的块,DBWR在写盘前,会执行所谓的提交清除(Commit Cleanout)——它会遍历块中的ITL列表,确保已提交事务的信息是清晰的。写盘后,该块的块SCN会被更新为检查点时的SCN。
- 如果一个查询需要读一个块,它会比较查询的SCN和块的SCN。如果块的SCN更大,说明这个块在查询开始后被修改过,那么它就必须使用ITL中记录的UBA去回滚段(Undo)中构造读一致性镜像。
三、 排查、诊断与解决方案
排查与诊断
-
识别ITL等待:
- 查询
V$SESSION_WAIT或V$ACTIVE_SESSION_HISTORY(ASH) /DBA_HIST_ACTIVE_SESS_HISTORY(AWR),寻找enq: TX - allocate ITL entry等待事件。
-- 当前正在等待ITL的会话 SELECT sid, serial#, event, state, seconds_in_wait FROM v$session WHERE event = 'enq: TX - allocate ITL entry'; -- 从AWR/ASH中查询历史ITL等待 SELECT sql_id, sample_time, session_id, program, module FROM dba_hist_active_sess_history WHERE event = 'enq: TX - allocate ITL entry' AND sample_time > SYSDATE - 1/24; -- 查询最近1小时 - 查询
-
定位热点对象:
- 找到等待事件后,可以通过
P1,P2,P3参数或结合V$SESSION中的ROW_WAIT_OBJ#来定位具体是哪个表或索引出现了争用。
SELECT s.sid, s.row_wait_obj#, s.row_wait_file#, s.row_wait_block#, o.owner, o.object_name, o.object_type FROM v$session s, dba_objects o WHERE s.event = 'enq: TX - allocate ITL entry' AND s.row_wait_obj# = o.object_id; - 找到等待事件后,可以通过
-
分析对象结构:
- 确认对象的
INITRANS和PCTFREE设置。
SELECT table_name, ini_trans, pct_free FROM dba_tables WHERE table_name = '&YOUR_TABLE'; SELECT index_name, ini_trans, pct_free FROM dba_indexes WHERE table_name = '&YOUR_TABLE'; - 确认对象的
解决方案
-
预防性设计(最有效):
- 合理设置
INITRANS: 对于已知并发度很高的表或索引,在建表/建索引时就应该设置一个较大的INITRANS值(例如 10, 20, 50)。这会在块初始化时预分配更多ITL槽,避免动态扩展的争用。 - 合理设置
PCTFREE:PCTFREE指定了块中为未来更新保留的空闲空间百分比。增大PCTFREE(例如从10增加到20),不仅为行更新预留了空间,也为ITL的动态扩展预留了头部空间。这对于更新频繁的表至关重要。
- 合理设置
-
补救性措施:
- 重组表(Table Reorganization): 对于已经存在大量ITL争用的表,可以通过
ALTER TABLE ... MOVE ...或在线重定义(DBMS_REDEFINITION)来重建表。重建后,所有数据块都会按照新的INITRANS和PCTFREE参数重新格式化,从而立即生效。ALTER TABLE your_table MOVE INITRANS 20 PCTFREE 20; -- 注意:MOVE操作会使索引失效,需要重建索引 - 重建索引: 索引块的ITL争用同样常见。
ALTER INDEX your_index REBUILD INITRANS 10;
- 重组表(Table Reorganization): 对于已经存在大量ITL争用的表,可以通过
-
应用程序优化:
- 减少热点块: 优化数据分布。如果并发事务总是操作不同的块,ITL争用就不会发生。可以考虑使用哈希分区(Hash Partitioning)将数据分散到更多的物理块上。
第二部分:通俗易懂的解释
让我们用一个生动的比喻来理解ITL机制。
把一个数据块想象成一个公司的会议室。
- 数据行: 会议室里的座位。
- 事务(Transaction): 需要开会的小组。
- ITL槽: 会议室门口的会议签到表上的签到格。每个格子对应一个会议小组。
会议流程(事务处理):
-
初始状态: 会议室门口有一张很小的签到表,只有2个格子 (
INITRANS=2)。会议室里还有很多空位 (Free Space)。 -
小组开会(事务修改):
- 销售组(Txn A)来了,他们在签到表上占用了第一个格子,写下自己的组名(XID)和会议笔记的存放地点(UBA),然后进会议室占了一些座位(修改行)。
- 市场组(Txn B)来了,占用了第二个格子,同样写下信息后进去开会。
-
并发冲突(ITL等待):
- 这时,研发组(Txn C)也来了要开会。他们一看签到表,两个格子都满了。
- 他们不会立即闯进去。他们会等待(
enq: TX - allocate ITL entry)。 - 他们可以等其中一个小组散会(提交)。散会的小组会在自己的格子里画个勾(标记为
COMMITTED),研发组就可以擦掉之前的信息, reuse (重用)这个格子。 - 如果会议室经理(Oracle)发现签到表旁边还有空地(块头部有空闲空间),他可能会立刻把签到表加大(ITL动态扩展),增加一个新的格子,让研发组签到。这样就不用等了。
-
问题根源:
- 如果会议室经理说:“签到表现有的大小已经占满了所有墙面,没法再扩大了”(块头部空间不足,无法动态扩展),那么研发组就只能干等着,直到有小组散会。这就是ITL等待。
-
会后清理(块检查点与提交清除):
- 清洁工(DBWR进程)不会在每个小组散会后就立刻进去打扫。他会等经理通知“今天下班了,做一次大扫除”(检查点触发)。
- 大扫除时,清洁工会检查所有签到格:对于已经画勾散会的小组,他会把他们的会议笔记归档;对于还没散会的小组,他也会简单记录一下。然后他把整个会议室的状态(包括签到表)拍一张照片,存档起来(写回数据文件)。这张照片的日期就是块SCN。
如何解决会议室拥堵?
-
提前准备(预防性设计): 如果你早知道这个会议室经常有3个以上的小组要同时开会,一开始就应该定制一张有10个签到格的大签到表 (
INITRANS=10),并确保门口墙面足够大 (PCTFREE=20),方便日后还能再加格子。 -
重新装修(重组表): 如果会议室已经因为设计不当而拥堵,最彻底的办法就是把所有人和东西暂时请出去,按照新的规格(更大的
INITRANS和PCTFREE)重新装修会议室,然后再让大家搬回来。
通过这个比喻,可以看到ITL争用是一种“基础设施性”的争用,它限制了块级别的并发事务数量。优化它的方法就是提前规划好“会议室”的容量和扩展性,或者在出现问题后进行彻底的重建。
欢迎关注我的公众号《IT小Chen》
Oracle数据块与ITL槽详解
917

被折叠的 条评论
为什么被折叠?



