
好的,作为一位Oracle数据库大师,我将为您深入解析Oracle数据库的基石之一:多版本读一致性(Read Consistency) 及其背后的支撑机制——UNDO 的管理与重用。这两者共同构成了Oracle高并发和数据一致性的核心。
第一部分:官方定义与核心作用
一、多版本读一致性 (Multi-Version Read Consistency)
- 官方定义:这是Oracle数据库的一种核心特性,它保证了一个查询在执行过程中,所看到的数据内容来自同一个时间点(通常是查询开始的那个时间点),而不会受到查询执行期间其他并发事务提交的修改所影响。这意味着读操作永远不会阻塞写操作,写操作也永远不会阻塞读操作。
- 核心作用:
- 提供非阻塞读:实现了极高的并发性,避免了读写冲突。
- 保证语句级一致性:单个查询看到的是一个一致的数据快照。
- 保证事务级一致性(默认隔离级别
READ COMMITTED):一个事务内的多个语句各自看到的是语句开始时的快照。SERIALIZABLE隔离级别下,整个事务看到的是事务开始时的快照。
二、UNDO 段 (Undo Segments) 与重用机制
- 官方定义:UNDO,也称为回滚(Rollback),是数据库自动管理的一种特殊段(Segment),用于存储数据被修改前的旧版本(前镜像 - Before Image)。这些信息主要用于:
- 实现事务回滚(Rollback)。
- 实现多版本读一致性。
- 提供数据库恢复(Recovery)。
- 核心作用:
- 事务回滚:当用户执行
ROLLBACK时,使用UNDO数据将修改还原。 - 读一致性:当查询需要访问被修改但未提交、或在查询开始后已提交的数据时,从UNDO中构造该数据块的“过去版本”。
- 实例恢复:在实例崩溃后重启时,用于回滚所有未提交的事务。
- 事务回滚:当用户执行
UNDO重用机制:UNDO表空间的空间是有限的。为了持续支持新事务,Oracle必须重用那些已经提交且不再被任何读操作需要的UNDO空间。这个管理过程是自动的,但理解其原理对排查经典错误ORA-01555: snapshot too old至关重要。
第二部分:深入底层原理与管理机制
一、多版本读一致性是如何工作的?
核心原理:“从UNDO段中重构历史版本的数据块”。
详细过程(以一个SELECT查询为例):
- 查询开始:当一个查询(
SELECT ...)开始时,它会获取一个当前的系统改变号(SCN),比如 SCN=1000。这个SCN就是该查询的“时间戳”。 - 读取数据块:服务器进程开始扫描数据块。对于它访问的每一个数据块,它都会检查块头部的ITL(事务槽) 和数据行锁信息。
- 判断是否需要构造一致性读版本:
- 场景A:块上的事务在查询SCN(1000)之后提交:数据块头记录的SCN(比如1001)大于查询SCN(1000)。这意味着该块在查询开始后被修改过。查询不能直接读这个“未来”的版本。
- 场景B:块上的事务在查询开始时还未提交:ITL中显示事务仍是活动状态。查询不能读未提交的脏数据。
- 从UNDO构造旧版本:
- 对于上述两种场景,服务器进程会根据数据块行或ITL中记录的UBA(Undo Block Address),像指针一样,在UNDO段中找到存储该数据前镜像的UNDO块。
- 它应用UNDO记录,将数据块在内存中“回滚”到查询SCN=1000时的状态。
- 这个在Buffer Cache中临时构造出来的、不属于任何数据文件的“虚拟”数据块,被称为一致性读(CR)克隆或一致性读版本。
- 返回结果:服务器进程从构造好的CR克隆中读取数据,返回给用户。整个过程对用户是透明的。
通俗比喻(时光机与历史档案):
- 数据块:世界的当前状态。
- UNDO段:一个详细记录了世界上每一次变化(谁,在什么时候,把什么从什么样子改成了什么样子)的历史档案馆。
- 查询SCN:一个历史时间点(例如,公元2020年)。
- 读一致性查询:你想知道2020年世界的样子。你拥有一台时光机(Oracle的读一致性机制)。你飞到今天(当前数据块),发现世界变了(SCN>2020)。于是你掏出档案馆的记录(UNDO),根据记录把今天的世界一点点“还原”回2020年的样子(构建CR块),然后进行观察。你看完后,这个还原出来的2020年的世界就消失了(CR块可能在Buffer Cache中被淘汰)。
二、UNDO段的管理与重用机制
UNDO段是循环使用的。其结构和管理非常精巧。
1. UNDO段结构:
- 一个UNDO表空间包含多个UNDO段(回滚段)。
- 每个UNDO段由多个区段(Extent) 组成。
- 每个区段由一系列连续的UNDO块组成。
- 事务会在一个UNDO段中分配一个事务槽(Transaction Slot),并顺序地使用UNDO块来记录其前镜像。这些UNDO块被组织成一个链式结构。
2. UNDO重用机制(空间事务槽管理):
- 活动(Active):该UNDO空间正在被一个活跃事务使用。这些空间绝对不能被覆盖,因为可能需要用于回滚。
- 未过期(Unexpired):该UNDO空间对应的事务已经提交,但其所包含的前镜像信息仍然有可能被某些尚未结束的长时间查询所需要,用于构建读一致性版本。这些空间通常也不能被覆盖。
- 过期(Expired):该UNDO空间对应的事务已经提交,并且已经没有任何正在运行的查询需要它来构建读一致性版本。这些空间是可以被新事务覆盖重用的“空闲”空间。
重用过程:
当一个新事务需要UNDO空间时,Oracle会优先寻找过期(Expired) 的UNDO空间来使用。如果找不到足够的过期空间,它就会尝试扩展UNDO段(如果表空间还有空闲),或者不得已去覆盖未过期(Unexpired)的UNDO空间。
UNDO_RETENTION参数:这个参数指导Oracle尽力将已提交的UNDO信息至少保留UNDO_RETENTION秒(默认900秒)。它设定了“未过期”的最小时间窗口。但这只是一个目标,并非绝对保证。在空间压力下,未过期的UNDO仍然可能被覆盖。
第三部分:原理串联与示例
场景: 一个长时间运行的报表查询 Q1 (SCN=1000) 和一个小型更新事务 T2。
T1:Q1开始,SCN=1000。T2:事务T2更新了表A中一行R1的数据,从100改为200。- 这个修改前的值
100被写入UNDO段。 - 数据块中的
R1被更新为200,其头部记录了指向UNDO的UBA。
- 这个修改前的值
T3:T2提交。T4:Q1的扫描进程终于读到了包含R1的数据块。- 它发现数据块上的SCN(
T2提交的SCN,假设是1005) > 它的查询SCN(1000)。 - 它根据数据块上的UBA,去UNDO段中找到
T2存放的前镜像值100。 - 它在内存中构建出一个CR克隆,在这个克隆中,
R1的值是100。
- 它发现数据块上的SCN(
Q1读取R1=100这个值,尽管当前数据块上的值已经是200。读一致性得到保证。
第四部分:争用、等待事件与排查解决
1. 经典的 ORA-01555: snapshot too old 错误
-
场景:这是最典型的UNDO问题。当一个查询(如
Q1)需要构建读一致性版本时,却发现它需要的那个前镜像所在的UNDO空间已经被新事务覆盖重用了。 -
根本原因:
- UNDO表空间过小或
UNDO_RETENTION设置太短,导致UNDO空间被过快重用。 - 长时间运行的查询与频繁提交的小事务大量交互。
- 过度循环的DML操作:对少量数据块进行反复更新,会产生很长的UNDO链,增加链断裂的风险。
- UNDO表空间过小或
-
影响:查询失败,无法完成。
-
排查:
-- 检查UNDO表空间使用率和自动扩展性 SELECT TABLESPACE_NAME, STATUS, SUM(BYTES)/1024/1024 "Size (MB)", ROUND(SUM(BYTES - NVL(FREE_BYTES, 0)) / SUM(BYTES) * 100, 2) "Used %" FROM DBA_UNDO_EXTENTS GROUP BY TABLESPACE_NAME, STATUS; -- 查看UNDO段统计信息 SELECT USN, EXTENTS, RSSIZE, HWMSIZE, XACTS, STATUS FROM V$ROLLSTAT; -- 查看当前UNDO保留情况 SELECT TO_CHAR(BEGIN_TIME, 'YYYY-MM-DD HH24:MI:SS') BEGIN_TIME, TO_CHAR(END_TIME, 'YYYY-MM-DD HH24:MI:SS') END_TIME, UNDOTSN, UNDOBLKS, TXNCOUNT, MAXQUERYLEN, MAXCONCURRENCY FROM V$UNDOSTAT;- 重点关注
V$UNDOSTAT中的MAXQUERYLEN(最长查询时间)和SSOLDERRCNT(发生ORA-01555的次数)。
- 重点关注
-
解决方案:
- 增大UNDO表空间。
- 增加
UNDO_RETENTION:将其设置为大于系统中最长查询的运行时间。 - 优化SQL:减少长时间查询的运行时间,避免不必要的全表扫描。
- 使用闪回查询(Flashback Query):对于预期需要查询历史数据的应用,使用
AS OF SCN或AS OF TIMESTAMP,它使用独立的闪回日志(如果配置了),更可靠。
2. UNDO段头争用 (Undo Segment Header)
- 场景:在高并发事务环境下,大量事务同时尝试在同一个UNDO段中分配事务槽(Transaction Slot)。
- 等待事件:
buffer busy waitsonsegment header类型。 - 排查:
SELECT CLASS, COUNT, TIME FROM V$WAITSTAT WHERE CLASS = 'undo header'; - 解决方案:
- 增加UNDO段的数量:默认的UNDO段数量可能不足。可以通过创建更大的UNDO表空间来间接增加(Oracle会自动创建更多UNDO段),或者在极少数情况下手动调整(需Oracle Support支持)。
- 使用自动UNDO管理(AUM):这是默认且推荐的方式,让Oracle自动管理UNDO段和空间。
3. UNDO块争用
- 场景:多个事务或会话同时访问同一个UNDO块。
- 等待事件:
buffer busy waitsonundo block类型。 - 解决方案:通常与UNDO段头争用解决方案一致,增加UNDO段数量以分散负载。
第五部分:常用监控SQL
-
监控UNDO表空间总体情况:
SELECT TABLESPACE_NAME, STATUS, ROUND(SUM(BYTES)/1024/1024, 2) "SIZE_MB", ROUND(SUM(BYTES) / (1024*1024) - SUM(FREE_BYTES)/(1024*1024), 2) "USED_MB" FROM DBA_UNDO_EXTENTS GROUP BY TABLESPACE_NAME, STATUS; -
查看当前活动事务使用的UNDO信息:
SELECT s.sid, s.serial#, s.username, t.used_ublk, t.used_urec, t.start_time FROM V$SESSION s, V$TRANSACTION t WHERE s.taddr = t.addr; -
评估并设置合理的UNDO_RETENTION:
-- 根据最长查询时间建议 retention SELECT MAX(MAXQUERYLEN) FROM V$UNDOSTAT; -- 然后将UNDO_RETENTION设置为略大于这个最大值(秒)
总结
官方总结:多版本读一致性是Oracle通过自动UNDO管理机制实现的标志性特性。它通过为查询维护一个基于SCN的时间点视图,并从UNDO段中重构数据块的历史版本来避免读写阻塞。UNDO空间通过“活动-未过期-过期”的状态机进行管理和循环重用。不当的配置和SQL设计会导致ORA-01555错误和段头争用。
通俗总结:Oracle就像一个永不关门的图书馆。
- 读一致性:允许读者(查询)随时借阅任何一本书(数据块)的特定历史版本复印件(CR块),而不用担心作者(写事务)正在旁边修改原稿。复印件是根据详细的修改记录(UNDO数据) 临时生成的。
- UNDO重用:图书馆的修改记录档案室(UNDO表空间)空间有限。管理员会定期粉碎(覆盖) 那些已经没人需要查阅的、过于陈旧的记录(过期的UNDO),以便记录新的修改。但如果一个读者想借阅一本非常老的书,而对应的修改记录刚被粉碎,他就会找不到资料(
ORA-01555)。
DBA的角色就是当好这个图书馆的后勤馆长:确保档案室足够大(UNDO表空间大小),制定合理的档案保存政策(UNDO_RETENTION),并提醒读者(优化SQL)不要总是提出要查阅非常古老的、难以保存的记录。
欢迎关注我的公众号《IT小Chen》

22万+

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



