Oracle 延迟块清除 内部原理、机制介绍和常用SQL

在这里插入图片描述
我将为您深入解析Oracle数据库中一个非常重要但常被忽视的机制:延迟块清除(Delayed Block Cleanout)。这个机制深刻体现了Oracle在性能与一致性之间所做的精妙权衡。


第一部分:官方定义与核心作用

一、官方定义 (Official Definition)

延迟块清除是Oracle的一种性能优化机制。当一个事务提交(COMMIT)时,它并不会立即去更新所有被它修改过的数据块,以清除其中的锁信息(ITL条目中的锁状态)并将块SCN更新为提交SCN。这个“清理”工作会延迟到后续的SQL语句首次访问这些数据块时才进行。

二、核心作用 (Purpose)
  1. 提升提交性能:提交操作的核心是LGWR进程将事务的重做记录(包括提交记录)同步写入磁盘重做日志文件。这是一个必须等待的I/O操作。如果提交时还需要DBWR进程将所有修改过的脏块都写入磁盘(或者即使在内存中遍历所有块进行清理),提交的响应时间将变得不可预测且极其漫长。延迟清除将提交操作简化为“写重做日志”这一件最关键的事,使其速度极快。
  2. 减少物理I/O:避免提交时产生大量的随机写I/O(去更新各个分散的数据块)。
  3. 批量处理:将块的清理工作分散到多个不同的后续操作中,并由执行这些操作的服务器进程来完成,实现了工作的负载均衡。

通俗解释:
想象一个顾客在超市购物(一个事务修改数据)。

  • 正常流程(如果没有延迟清除):顾客结账(事务提交)时,收银员不仅要把账单记下来(写重做日志),还要跑回货架,把每个被买走的商品的位置整理好,贴上“已售出”的标签(清除块上的锁信息)。这会使得结账队伍移动得非常慢。
  • 延迟清除流程:顾客结账时,收银员只需快速记下账单(写重做日志)就算完成。之后,当其他顾客(后续查询)走到货架前想拿商品时,如果发现商品被买走了但还没整理,这个顾客会自己动手简单整理一下(执行延迟块清除)。这样,结账窗口就永远不会堵塞。

第二部分:深入底层原理与管理机制

要理解延迟块清除,必须首先理解两个核心数据结构:事务槽(ITL)提交SCN

一、前置知识:数据块结构与事务槽(ITL)
  • 事务槽(ITL - Interested Transaction List):在每个数据块的头部,都有一个区域用于记录哪些事务正在修改这个块。每个活动事务都会在其中占用一个“槽位”(ITL Entry)。
  • ITL条目内容:主要包括Uba(Undo Block Address,指向回滚段中保存前镜像的地址)、Xid(事务ID)、Flag(状态标志,如事务是否已提交)、Lck(该事务在块内锁定了多少行)等信息。
  • 提交SCN:事务提交时,会被赋予一个唯一的SCN号。
二、延迟块清除的详细过程

第1步:事务进行中

  1. 一个事务更新了块B中的一行数据。
  2. 服务器进程在块B的ITL中找到一个空闲槽位,将其标记为活动状态,填入XidUba等信息,并将Lck计数加1。
  3. 数据块B变成了脏块,被挂到检查点队列上。

第2步:事务提交

  1. 用户执行COMMIT
  2. LGWR进程将事务的重做记录(包含提交信息)同步写入重做日志文件。这个动作是提交过程中的唯一等待点(log file sync)。
  3. 在事务所属的回滚段头的事务表中,将该事务的状态标记为已提交,并记录下它的提交SCN
  4. 关键点:此时,数据库不会去遍历所有被修改过的数据块(如块B)来更新它们的ITL。块B中的ITL条目仍然显示该事务为“活动”状态。

第3步:后续访问触发清除(延迟清除的发生)

  1. 之后,另一个会话发起了一条SELECT语句,需要读取块B。
  2. 服务器进程将块B从磁盘读入Buffer Cache。
  3. 进程在块B的ITL中看到了一个“活动”的事务槽,但它需要读这个块。为了判断该事务是否已提交,它必须进行**“块清除”**:
    • a) 检查回滚段头:服务器进程根据ITL中的Xid,找到事务所属的回滚段。
    • b) 查找事务表:在回滚段头的事务表中,查找该Xid对应的事务状态。
    • c) 获取信息:发现事务已提交,并获取其提交SCN
    • d) 清理块:服务器进程在内存中(Buffer Cache里)对块B执行以下操作:
      • 将ITL条目中的Flag标记为“已提交”。
      • 将ITL条目中的SCN字段更新为事务的提交SCN
      • Lck计数清零。
    • e) 生成重做注意!这个清理块ITL的操作本身也会产生重做日志记录,以确保这个清理动作在实例崩溃后可以重演。
  4. 现在,块B被清理干净了,会话可以安全地读取其中的数据。这个块也变成了一个“干净”的块,但因为它被修改了(清理了ITL),所以它又变成了一个延迟块清除产生的脏块,会被再次挂到检查点队列上,最终由DBWn写入磁盘。
三、特殊情况:提交列表清除(Commit List Cleanout)

如果一条SELECT语句需要读取大量的块,而这些块都被同一个已提交的事务修改过,那么逐块去回滚段头检查会非常低效。因此,Oracle做了一个优化:

  1. 服务器进程首先会批量获取一批需要清理的块的事务信息。
  2. 它会在PGA中构建一个提交列表(Commit List),包含这些事务的Xid和提交SCN。
  3. 当真正处理每个块时,它就不需要再去回滚段头查询,而是直接从这个PGA中的提交列表里获取提交SCN来完成清理,大大提升了效率。

第三部分:原理串联与示例

场景: 事务A(SCN=1000时)更新了表T的1000个数据块,然后提交。

  1. 提交瞬间LGWR将事务A的提交记录写入重做日志。回滚段头事务表标记事务A为已提交,提交SCN=1001。1000个数据块的ITL均未被更新
  2. 后续全表扫描:用户B运行SELECT COUNT(*) FROM T;
  3. 触发清除:服务器进程读取第一个块。
    • 发现ITL中有活动事务Xid=A。
    • 前往回滚段头,查得事务A已提交,SCN=1001。
    • 在内存中将该块的ITL状态更新为已提交,SCN=1001,并生成重做日志。
    • 读取该块。
  4. 优化工作:进程意识到可能需要清理很多这样的块,于是在PGA中建立提交列表,记录{Xid: A, Commit SCN: 1001}
  5. 批量清除:读取后续的999个块时,进程不再访问回滚段头,而是直接使用PGA中的提交列表信息来快速清理每个块的ITL。
  6. 副作用:这个全表扫描操作本来是一个只读查询,却因为延迟清除机制,实际上修改了这1000个块(更新了ITL),产生了重做日志,并将这些块再次变为脏块,增加了DBWn的写负载。

第四部分:争用、等待事件与排查解决

延迟块清除机制本身是为了避免争用,但它可能将争用转移到别处。

1. 回滚段头争用 (Undo Segment Header)
  • 场景:当大量会话同时触发延迟块清除,都需要访问同一个回滚段头块来查询事务状态时。
  • 等待事件buffer busy waitsread by other session(在回滚段头块上)。
  • 排查SQL
    SELECT * FROM V$WAITSTAT WHERE CLASS IN ('undo header', 'segment header');
    -- 如果等待次数很高,说明存在争用
    
    SELECT EVENT, P1, P2, P3
    FROM V$SESSION_WAIT
    WHERE EVENT LIKE '%buffer busy%' OR EVENT = 'read by other session';
    -- 然后使用P1(文件号), P2(块号)定位争用的块
    SELECT NAME FROM V$DATAFILE WHERE FILE# = &P1;
    
  • 解决方案
    • 增加回滚段数量:默认情况下,Oracle会根据系统活动自动调整UNDO表空间中的回滚段数量。但在极高并发下,可以尝试手动增加UNDO_TABLESPACE的回滚段数量(通过_CORRUPTED_ROLLBACK_SEGMENTS_OFFLINE_ROLLBACK_SEGMENTS参数,需极端谨慎,最好在Oracle Support指导下进行),以分散访问压力。
    • 使用自动UNDO管理:确保使用UNDO_TABLESPACE,让Oracle自动管理回滚段,这通常能提供最佳实践。
2. 读性能突然下降
  • 场景:一个大的查询(如报表查询)在访问一个刚刚被大批量DML操作(如数据加载)修改并提交后的表。
  • 影响:该查询需要承担额外的“块清除”工作,包括访问回滚段和更新数据块ITL。这会导致该查询的执行时间远长于预期。它本来是个只读操作,却产生了写操作(重做日志)。
  • 排查:对比该查询在系统空闲时和批量作业刚结束时的执行计划(DBMS_XPLAN.DISPLAY_CURSOR)和统计信息(STATISTICS LEVEL = ALL),观察cr blocks created(一致性读块创建)等指标是否异常高。
  • 解决方案
    • 在批量操作后手动收集统计信息:这其实是一个触发块清除的很好时机。DBMS_STATS.GATHER_TABLE_STATS过程在读取数据时就会完成块清除工作。
    • 在批量操作后运行一个全表扫描:例如SELECT COUNT(*) FROM <table_name>;,目的就是让这个无害的查询提前完成所有延迟块清除工作,避免影响后续真正的业务查询。
    • 优化提交频率:在批量数据处理中,不要每一条记录提交一次,但也不要一个事务太大。找到一个平衡点,避免产生需要清理的巨量块。
3. 空间与性能开销
  • 影响:延迟清除会产生额外的重做日志(用于记录ITL的更新操作)和额外的脏块(被清理过的块需要再次被DBWn写盘)。
  • 监控:关注AWR报告中的**“IMU undo recovery”** 指标,如果很高,说明系统花费了大量资源在延迟清除上。

第五部分:常用监控SQL

  1. 查看系统层面的延迟清除统计

    SELECT NAME, VALUE
    FROM V$SYSSTAT
    WHERE NAME LIKE '%cleanout%';
    
    • commit cleanouts:提交时完成的清除次数(非延迟清除)。
    • commit cleanouts successfully completed:成功完成的清除次数。
    • deferred (CUR) block cleanout applications:延迟清除发生的次数。这个值很高意味着系统正在处理大量延迟清除。
  2. 识别正在经历大量读操作的表(可能是延迟清除的候选对象):

    SELECT OWNER, OBJECT_NAME, OBJECT_TYPE, VALUE
    FROM V$SEGMENT_STATISTICS
    WHERE STATISTIC_NAME = 'logical reads'
    ORDER BY VALUE DESC;
    
  3. 监控回滚段头争用(如前所述):

    SELECT CLASS, COUNT, TIME
    FROM V$WAITSTAT
    WHERE CLASS IN ('undo header', 'segment header');
    

总结

官方总结:延迟块清除是Oracle数据库的一项核心性能优化特性。它通过将事务提交时的块清理工作推迟到后续的第一次数据访问时进行,极大地减少了提交操作的响应时间和物理I/O。该机制依赖于回滚段头中的事务状态信息和数据块头部的ITL条目协同工作。

通俗总结“事后打扫”。Oracle让“提交”这个动作变得极快(只记日志),然后把“打扫战场”(更新数据块)的杂活交给后面“路过的人”(后续查询)来做。这虽然极大地提高了结算(提交)速度,但偶尔会导致路过的人(查询)走得慢一点(性能下降),或者打扫工具(回滚段头)被很多人争抢(争用)。DBA需要意识到这种机制的存在,并在它可能引发问题时(如批量作业后),主动安排“大扫除”(全表扫描),从而避免其对关键业务查询造成影响。

欢迎关注我的公众号《IT小Chen

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值