本篇文档主要讨论什么情况下会产生ORA-01555快照过旧情况,以及如何避免ORA-01555报错情况,以及通过案例去分析ORA-01555,进行问题的讨论与分析。

1.1 oracle官方术语
undo segment和scn以及延迟块清除说明,以下是Oracle官方对撤销段以及读一致性的解释说明:
Read Consistency and Undo Segments To manage the multiversion read consistency model, the database mustcreate a read-consistent set of data when a table is simultaneouslyqueried and updated. Oracle Database achieves this goal through undodata.
Whenever a user modifies data, Oracle Database creates undo entries, which it writes to undo segments ("Undo Segments"). The undo segments contain the old values of data that have been changed by uncommitted or recently committed transactions. Thus, multiple versions of the same data, all at different points in time, can exist in the database. The database can use snapshots of data at different points in time to provide readconsistent views of the data and enable nonblocking querie.
Read consistency is guaranteed in single-instance and Oracle Real Application Clusters (Oracle RAC) environments. Oracle RAC uses a cacheto-cache block transfer mechanism known as Cache Fusion to transfer read-consistent images of data blocks from one database instance toanother.
为管理多版本的读取一致性模型,当表同时被查询和更新时, 数据库必须创建一组读取一致的数据。 Oracle 数据库通过使用撤销数据实现了这一目标。每当用户修改了数据, Oracle 数据库会创建撤销条目,并写入到撤销段 ("撤销段")。 撤销段包含由未提交事务或最近提交的事务所更改的数据的旧值。因此, 同一数据在各个不同时间点上的多个版本,都可以存在于数据库中。数据库可以使用在不同时间点的数据快照, 来提供数据读取一致视图,并实现非阻塞查询读取一致性在单实例和 Oracle 真正应用集群 ( Oracle RAC) 环境中都可以得到保证。 Oracle RAC 使用一种称为缓存融合的“ 缓存到缓存” 的数据块 传输机制, 将一个数据库实例中的数据块读取一致映像传送到另一个实例中。
1.2 SCN
System Change Numbers (SCNs) A system change number (SCN) is a logical, internal time stamp used by Oracle Database. SCNs order events that occur within the database, which is necessary to satisfy the ACID properties of a transaction. Oracle Database uses SCNs to mark the SCN before which all changes are known to be on disk so that recovery avoids applying unnecessary redo. The database also uses SCNs to mark the point at which no redo exists for a set of data so that recovery can stop. SCNs occur in a monotonically increasing sequence. Oracle Database can use an SCN like a clock because an observed SCN indicates a logical point in time and repeated observations return equal or greater values. If one event has a lower SCN than another event, then it occurred at an earlier time with respect to the database.
Several events may share the same SCN, which means that they occurred at the same time with respect to the database.
系统更改号 (SCN) 是一个由 Oracle 数据库使用的逻辑、 内部的时间戳。SCN 按数据库中发生的事件排序,以满足在事务 ACID 属性的需要。 Oracle数据库使用 SCN 来标记某个位置, 在其之前的所有更改都被认为已写到磁盘上, 以避免在恢复过程中应用不必要的重做。 数据库还使用 SCN 来标记一个其数据不存在重做信息的点,以便恢复过程可以在该点停止SCN 是按单调递增的顺序发生的。 Oracle 数据库可以像使用时钟一样使用 SCN,因为一个 SCN 观察值指示一个逻辑时间点, 而其重复观察值相比之 前会相同或更大。 若一个事件的 SCN 比另一个事件的 SCN 低,则它在数据库中发生在一个更早的时间。几个事件可以共享相同的 SCN, 这意味着他们在数据库中同时发生。
每个事务都有一个 SCN。 例如, 如果事务更新了一行,则数据库记录此更新发生时的 SCN。 此事务中其他修改具有相同的 SCN。当事务提交时,数据库将为此提交记录一个 SCN
Oracle 数据库在系统全局区域( SGA) 中递增 SCN。事务中修改数据时, 数据库会将一个新的 SCN 写入到分配给事务的撤销数据段。 然后日志写入器进程立即将事务的提交记录写入在线重做日志。提交记录具有事务的唯一
SCN。 Oracle 数据库还使用 SCN 作为其实例恢复和介质恢复机制的一部分。
1.3 延迟块清除
Delayed Block Cleanout:This is best illustrated with an example: Consider a transaction that updates a million row table. This obviously visits a large number of database blocks to make the change to the data.
When the user commits the transaction Oracle does NOT go back and revisit these blocks to make the change permanent. It is left for the next transaction that visits any block affected by the update to 'tidy up' the block (hence the term 'delayed block cleanout').
Whenever Oracle changes a database block (index, table, cluster) it stores a pointer in the header of the data block which identifies the rollback segment used to hold the rollback information for the changes made by the transaction. (This is required if the user later elects to not commit the changes and wishes to 'undo' the changes made.) Upon commit, the database simply marks the relevant rollback segment header entry as committed. Now, when one of the changed blocks is revisited Oracle examines the header of the data block which indicates that it has been changed at some point. The database needs to confirm whether the change has been committed or whether it is currently uncommitted. To do this, Oracle determines the rollback segment used for the previous transaction (from the block's header) and then determines whether the rollback header indicates whether it has been committed or not.
延迟块清除:最好通过示例说明:考虑更新百万行表的事务。这显然会访问大量数据库块以对数据进行更改。
当用户提交事务时,Oracle不会返回并重新访问这些块以使更改成为永久更改。剩下的事务是访问受更新影响的任何块以“整理”块(因此称为“延迟块清除”)。
每当Oracle更改数据库块(索引,表,集群)时,它都会在数据块的标头中存储一个指针,该指针标识用于保存事务所做更改的回滚信息的回滚段。 (如果用户稍后选择不提交更改并希望“撤消”所做的更改,则这是必需的。)提交时,数据库只是将相关的回滚段头条目标记为已提交。现在,当重新访问其中一个更改的块时,Oracle会检查数据块的标头,指示它在某个时刻已被更改。数据库需要确认更改是否已提交或当前是否未提交。为此,Oracle确定用于上一个事务的回滚段(来自块的标头),然后确定回滚标头是否指示它是否已提交。
1.4 undo参数说明

注:
undo_management表示回滚段管理方式,值可以为MANUAL/AUTO。9i中默认是MANUAL,10g中默认是AUTO,从9i后,回滚段就以表空间的形式管理,并且支持系统自动管理回滚段。一个回滚表空间上可以创建多个回滚段,一个数据库可以创建 多个回滚表空间。但是,一个实例(Instance)只能使用一个回滚表空间undo_retention表示:设置回滚段中的被提交或回滚的数据强制保留时间,单位是秒这个参数和1555错误有非常大的关系。但是,需要提醒的是,并不是回滚段中的数据超过这个时间以后就会被清除掉,而是等到后面事务产生的回滚数据覆盖掉“超期”数据。所以这就是为什么我们往往看到系统的回滚表空间占有率始终是100%的原因了
----------------------------------------------------------------------------------------------------------------------------
2 oracle 逻辑读概念分析和工作原理理解
2.1 逻辑IO
逻辑IO是大量消耗cpu的操作,在awr load profile指标明确标注逻辑读在两次快照范围内生成情况,Logical Read,单位为:单位 次数*块数,逻辑读高则DB CPU往往高,也往往可以看到latch: cache buffer chains等待,同时对应awr报告中,% Blocks changed per Read指标每次逻辑读导致数据块变化的比率,如果’redo size’, ‘block changes’ ‘pct of blocks changed per read’三个指标都很高,则说明系统正执行大量insert/update/delete同时还可以关注awr报告中session logical reads,这里,简单介绍一下db block gets 、consistent gets 以及 session logical reads的关系:
db block gets=db block gets direct+ db block gets from cache
consistent gets=consistent gets from cache+ consistent gets direct
consistent gets from cache= consistent gets – examination + else
consistent gets – examination==>指的是不需要pin buffer直接可以执行consistent get的次数,常用于索引,只需要一次latch get同时还可以关注awr报告中的Service Statistcs指标,有具体Logical的产生情况,单位为每KB,表示本服务所消耗的逻辑读。同时还可以到Segments by Logical Reads 指标中关注Logical Reads :该数据段上发生过的逻辑读,针对逻辑读还可以关注Global Messaging Statistics指标,全局通信统计信息会记录逻辑读的情况。在集群环境还可以关注Global Cache Efficiency Percentages指标中的Buffer access – local cache %,这个指标表示数据块从本地缓存命中占会话总的数据库请求次数的比例,以上为通过awr报告定位逻辑读产生的指标关注,见招拆招,,回到刚才的话题我们在2.2中继续介绍。
2.2 一致性读和当前读
逻辑读被分为一致性读和当前读两种情况,在v$sysstat中有consistent gets和db block gets两个指标的具体参数消耗情况,一致性读针对select,而当前读则是针对dml和ddl语句,在shared pool工作原理中,注意,select语句只会申请一致性读模式的buffer pin lock,而dml ddl会申请当前读模式和修改模式的buffer pin lock.对于dml的当前读模式和select的一致性读模式他们并不会互相阻塞,但是dml和ddl的当前读模式是会互相阻塞的,这样做的目的是还处在定位要修改行位置的dml操作,并不会阻塞select,但是却可以阻塞其他dml操作。来看以下简单的试验:
2.2.1 session A完成如下操作:

session B查询逻辑读消耗情况

注意:
一致性读除了consistent gets还有诸如consistent gets examination (fastpath),consistent gets examination,consistent gets direct,consistent gets from cache,consistent gets pin性能指标,其中consistent gets examination就是cache buffer chain latch进行的逻辑读,索引无论是根块,枝块还是叶子到表级别的blocks都是基于共享的cache buffer chain latch方式进行逻辑读。当我们做如下操作会发现。
session A:

注意:
pl/sql匿名块筛选where rn=1,rn上有唯一索引,每次只会查询出一行,因此,如上面所赘述的内容,每次的cache buffer chain latch都将是共享的latch,而且每次逻辑读需要申请一次cache buffer chain latch,因此得出一个结论,基于索引每次读其实都是consistent gets examination,因为consistent gets没有发生任何变化。
2.3 减少逻辑读
如何减少逻辑读,这是一个很大概念,我们从工作原理做一个简要分析,为了帮助600成员能够了解oracle内部工作原理:

注意:
2.3.1 在不改变sql语句的情况下是否可以降低逻辑读?
我们说oracle如何在内存中找到一个buffer ,读buffer时候,首先会申请一个cache buffer chain latch,然后是buffer pin lock,在buffer pin lock的作用下对应的进程将buffer 中的行读取到pga内存,然后释放buffer pin lock 关于buffer 的内容本文档不扩展赘述,假设我们要读取7499 ALLEN内容,oracle要从buffer cache中做内存复制将7499 ALLEN的数据复制到pga中,基于pro oracle c++中memcpy接口函数,它的作用是将N个字节大小的内存从一个区域复制到另外一个区域。
2.3.2 逻辑读的行
假设一个oracle blocks有100行,一次逻辑读会从块中复制多少行到pga,简单说就是一次性的逻辑读能够读取多少行。这时候就需要考虑oracle预读取的特性也就是prefetch特性,缺省情况下oracle预读取的行数为15行,针对当前会话我们可以做如下操作
SQL> show arraysize;
arraysize 15
SQL> set arraysize 100;
SQL>
当我们批量预抓取对应数据行到pga中,服务器进程将持有cache buffer chain latch然后在latch的保护下获得一致性模式的buffer pin,接着,服务器进程将会等待sql* net message to client等待事件,然后客户端开始
接受行源生成器返回的数据,再然后服务器进程开始复制数据,100行数据从buffer 读入pga内存,然后服务器进程释放buffer pin lock,当然释放buffer pin lock仍在还需要cache buffer chain latch的内存机制保护下进行
这中间设计到sdu的概念,本文档对oracle sdu概念不做赘述,感兴趣600成员可以关注oracle net services administrator guide中关于optimizing performance中关于session data unit的概念介绍。来看以下试验:



注:
这个简单的测试结果不做赘述,大家都能看得懂,我们主要是解释Oracle逻辑读工作原理浅析以及buffer到pga内存复制的过程。
2.4 1555错误的主要原因
一致性读(Consistent Get)可以说是产生1555错误的主要原因,在标准SQL中,为了防止并发事务中产生脏读,就需要通过加锁来控制。这样就会带来死锁、阻塞的问题,即时是粒度最小的行级锁,也无法避免这些问题,为了解决这一矛盾。Oracle充分利用的回归段,通过会滚段进行一致性读取,即避免了脏读,又大大减少了系统的阻塞、死锁问题。下面就看下Oracle是如何实现一致性读的。
当Oracle更新数据块(Data Block Oracle中最小的存储单位)时,会在两个地方记录下这一更新动作。一个是重做段(Redo Segment),是用于数据库恢复(Recover)用的。一个是回滚段(UNDO Segment),而回滚段是用于事务回滚(Rollback)的(我们只关心回滚段了)。并在数据块头部标示出来是否有修改数据。一个语句在读取数据快时,如果发现这个数据块是在它读取的过程中被修改的(即开始执行读操作时并没有被修改),就不直接从数据块上读取数据,而是从相应的回滚段条目中读取数据。这就保证了最终结果应该是读操作开始时的那一时刻的快照(snapshot),而不会受到读期间其他事务的影响。这就是Oracle的一致性读,也可以叫做多版本(Multi-Versioning)
Oracle的多版本模型会使用undo段数据依照语句或事务开始时的原样来重建块(究竟是语句还是事务,这取决于隔离模式) 。如果必要的undo信息不再存在,你就会收到一个ORA-01555: snapshot too old错误消息,查询也不会完成。所以,如果像前面的那个例子一样,你一边在读取表,一边在修改这个表,就会同时生成查询所需的undo信息。 UPDATE生成了undo信息,你的查询可能会利用这些undo信息来得到待更新数据的读一致视图。如果提交了所做的更新,就会允许系统重用刚刚填写的undo段空间。如果系统确实重用了undo段空间,擦除了旧的undo数据(查询随后要用到这些undo信息) ,你就有大麻烦了。 SELECT会失败,而UPDATE也会中途停止。这样就有了一个部分完成的逻辑事务,而且可能没有什么好办法来解决。
通常我的经验是,在执行一次,一般都会好,工作原理在这里不做解释,来看一个小案例:

--创建一个非常小的undo表空间,修改系统参数指定该表空间,注意此处需要把undo autoextend设置为off,防止自动扩展,限制Undo大小为2M
CREATE UNDO TABLESPACE TBS_UNDO_TEMP DATAFILE'C:\APP\WANGRONG\ORADATA\ORACLE\TBS_UNDO_TEMP.DBF' SIZE 2M AUTOEXTEND OFF;
--CDB模式指定Undo表空间
ALTER SYSTEM SET UNDO_TABLESPACE=TBS_UNDO_TEMP;
--执行一个简单的匿名块:

注意:
收到了这个错误。应该指出,这里向查询增加了一个索引提示以及一个WHERE子句,以确保随机地读取这个表(这两方面加在一起,就能使基于代价的优化器读取按索引键“ 排序”的表) 。通过索引来处理表时,往往会为某一行读取一个块,可是我们想要的下一行又在另一个块上。最终,我们确实会处理块1上的所有行,只不过不是同时处理。假设块1可能包含OBJECT_NAME以字母A、 M、 N、 Q和Z开头的所有行的数据。这样我们就会多次命中(读取)这个块,因为我们在读取按OBJECT_NAME排序的数据,而且可能有很多行的OBJECT_NAME以A~M之间的字母开头。 由于我们正在频繁地提交和重用undo空间,最终再次访问一个块时,可能已经无法再回滚到查询开始的那个时间点,此时就会得到这个错误。
这是一个特意构造的例子,纯粹是为了说明如何以一种可靠的方式发生这个错误。 UPDATE语句正在生成undo信息。我只能用一个很小的undo表空间(大小为2MB) 。这里多次在 undo段中回绕,因为undo段要以一种循环方式使用。每次提交时,都允许Oracle覆盖前面生成的undo数据。最终,可能需要某些已生成的undo数据,但是它已经不在了(即已经被覆盖) ,这样就会收到ORA-01555错误
----------------------------------------------------------------------------------------------------------------------------
3 延迟块清除导致ORA-01555:块清除( block cleanout) ,即生成所修改数据库块上与“ 锁定” 有关的信息
3.1
在Oracle中数据锁(这里主要指TX类型行锁)实际上是数据的属性,存储在块首部,称之为事务槽(ITL)。COMMIT操作的职责包括释放块上的锁,实际的释放方式即清除块上相应的事务槽,但这里存在一个性能的考量。 设想一个UPDATE大量数据的操作,因为执行时间较长,一部分已修改的块已被缓冲池flush out写至磁盘,当UPDATE操作完成执行COMMIT操作时,则需要将那些已写至磁盘的数据块重新读入,这将消耗大量I/O,并使COMMIT操作十分缓慢;为了解决这一矛盾,Oracle使用了延迟块清除的方案,对待存在以下情况的块COMMIT操作不做块清除
3.1.1
在更新过程中,被缓冲池flush out写至磁盘的块
3.1.2
若更新操作涉及的块超过了块缓冲区缓存的10%时,超出的部分块。
3.1.3
虽然COMMIT放弃对这些块的块清除(block cleanout)操作,但COMMIT操作仍会修改回滚段的段头,回滚段的段头包括了段中的事务的字典,COMMIT操作将本事务转化为非ACTIVE状态。
3.1.4
当下一次操作如SELECT,UPDATE,INSERT或DELETE访问到这些块时可能需要在读入后完成块清除,这样的操作称之为块延迟清除(deferred block cleanout);块延迟清除通过事务槽上的回滚段号,槽号等信息访问回滚段头的事务字典,若事务不再活跃或事务过期则完成清除块上的事务槽,事务槽清除后继续执行相应的操作。
3.1.5
块延迟清除的影响在SELECT操作过程中体现的最为明显。总结来说块延迟清除是COMMIT操作的一个延续,始终是一种十分轻微的操作,且该种操作是行级的,不会使段(Segment)的属性有所改变。
3.2 在Oracle中如何让SELECT查询绕过UNDO
3.2.1 参数
原则上,在Oracle官方文档或者Asktom的时候显然会提到Oracle是不实现脏读的, 总是有undo来提供数据块的前镜像以维护一致性Consistent, 通过正常途径我们几乎不可能破坏Oracle中查询的一致性来实现脏读,这里给各位介绍一种方法,取巧的方法:我们知道Undo有两个_offline_rollback_segments or _corrupted_rollback_segments 参数,这2个隐藏参数对于熟悉Oracle数据库异常恢复或者解决ORA-600应该不陌生,因为这2个参数是针对Undo存在Corruption讹误时忽略问题的有力工具。一般强制打开数据库,控制一致性读和延迟块清除,或者是强制删除某个rollback segment回滚段可以用到这两个参数。

注意:
10513 level 2 event可以禁止SMON 回滚rollback 死事务 dead transaction
_in_memory_undo 禁用 in memory undo 特性

session A:

注意:
在没有活跃事务的情况下,直接读取current block,全表扫描一致性读,consistent gets只要3次

这里session A不commit;
另开一个 session:

注意:
为了一致性读 上面的查询需要通过undo构造CR块,这导致consistent gets上升到 505

杀掉session A对应的Server Process服务进程,这导致dead transaction 但是不被smon回滚

此时有1个active rollback segment

注意:
到上面为止 虽然通过kill进程 和禁止smon 回滚dead transaction ,形成了一个不回滚的死事务 但是仍通过undo实现了一致性读
找出当前active的rollback segment的名字

用 _corrupted_rollback_segments 废掉 上面的2个rollback segment, 这将导致无法提供undo


注意:
以上可以看到 consistent gets下降到3,服务进程读取数据块发现存在活跃事务,但是ITL指向的UNDO SEGMENTS在_corrupted_rollback_segments的列表中,所以直接认为该事务已经COMMIT提交,以便绕过UNDO




----------------------------------------------------------------------------------------------------------------------------
未完再续
----------------------------------------------------------------------------------------------------------------------------




