1. 概述:备库查询弹出ORA-01555,问题可能比你想象的复杂
做DBA的人,对ORA-01555应该都不陌生——快照过旧,典型的UNDO不够用或者查询时间太长导致的一致性读失败。但大部分人对这个报错的认知都停留在主库上,觉得备库只承担只读查询,又不产生事务,怎么会冒出来01555?
我在实际运维中接过不少这样的工单,现象非常一致:应用通过ADG备库跑某个批处理报表,查询跑了二三十分钟,突然报出ORA-01555: snapshot too old,应用直接失败。主库上同样的SQL好好的,备库却偶发报错,而且重启备库、清理会话都没用,过几天又冒出来。
这个问题的根子,在于你下意识地把备库当成了“一个性能稍弱的主库”来理解,但ADG备库在UNDO管理上和主库有本质差异。它不只是性能弱,而是UNDO段的行为逻辑都不一样。要真正解决备库上的01555,不能只是加大UNDO表空间、调大undo_retention这么简单,尤其是在Oracle 19c之后的ADG环境下,有更优雅也更彻底的方案。
这篇文章我就从问题产生的底层机制聊起,把备库只读查询下UNDO的工作原理、常见误区和一条完整的优化路径都梳理清楚,最后附上可以直接拿去用的监控和排查脚本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 备库出现ORA-01555的根本原因:为什么备库明明“不写”却会快照过旧
2.1 快照过旧的本质是UNDO被覆盖,而不是UNDO空间用完
要理解备库为什么会产生ORA-01555,先得捋清楚这个报错的机制到底是什么。
Oracle的多版本读一致性(MVCC)模型下,一个查询开始的那一刻,数据库会记录一个查询SCN。查询每读取一个数据块,都需要保证看到的是这个SCN时刻的数据版本。如果数据块已经被其他事务修改,数据库就去UNDO段里找修改前的镜像,一层一层往回翻滚,构造出查询SCN时刻的版本。这个过程就是一致性读(Consistent Read,CR)。
问题来了:UNDO段是循环使用的。一个事务提交之后,它产生的UNDO信息并不会立刻被清理,而是要等没有任何活跃查询还需要它的时候才能被覆盖。如果某个查询运行时间太长,或者某个小UNDO表空间撑不住高并发的事务量,后续的事务就会把前面已经提交事务的UNDO覆盖掉。这时候那个长时间运行的查询再想去读历史版本,发现UNDO里已经找不到了,数据库就只能抛出ORA-01555。
注意这里有个关键点:ORA-01555和UNDO表空间满不满没有必然关系。UNDO表空间即使使用率只有30%,只要某个回滚段里的数据被覆盖了,一样会报01555。所以网上很多“扩UNDO表空间”的建议,其实只解决了空间不够导致的覆盖问题,没有解决根本的“覆盖行为”。
2.2 ADG备库的UNDO机制:非本地事务产生的UNDO,备库管不了
主库上的UNDO是主库自己的事务生成的,自己管理、自己清理,逻辑上是闭环的。但ADG备库完全不一样。
ADG备库通过日志应用(Managed Recovery)重放主库传过来的Redo日志,从而在备库上同步数据。Redo日志里包含了数据块的变更,也包括UNDO段的变更。也就是说,备库上的UNDO段内容和主库是完全一致的——主库产生多少UNDO,备库就会应用多少UNDO。备库上的UNDO,实际上是在重放主库的事务时被动生成的,备库对UNDO段没有自主管理权。
这就导致了一个在备库上非常棘手的局面:备库上的UNDO段能否被覆盖,取决于主库上对应UNDO段的覆盖情况。主库如果因为某些原因提前覆盖了UNDO,备库同步应用后也会把这部分UNDO覆盖掉。而备库上的只读查询会话,在这期间可能还在使用那些已经被覆盖的UNDO来做一致性读。
打个不严谨但好理解的比方:主库是图书馆的管理员,负责决定哪些旧报刊留档、哪些可以清理丢掉;备库是另一个阅览室,管理员的所有清理动作都会同步执行过来。如果阅览室里有个读者正捧着一本已经被管理员判定“可以丢”的旧报纸在仔细研读,那对不起,报纸被丢掉的那一刻,读者手里的内容就残缺了。
更复杂的情况是Data Guard的日志应用存在延迟。备库在应用日志时不会考虑备库本地有多少长查询在跑,它只会按照主库日志里的时间线一板一眼地重放。所以备库上运行得越久的只读查询,就越容易撞上一个尴尬时刻——它需要读取的UNDO信息刚好被备库应用过来的Redo覆盖掉。
2.3 “最小补充日志”与延迟清除:一个经常被忽略的辅助因素
除了UNDO覆盖这个核心原因,还有一个参数在ADG场景下会加重01555的概率——DELAY和UNDO_RETENTION在备库上的表现差异。
主库上设置UNDO_RETENTION,代表的是“已提交的UNDO信息至少保留N秒不覆盖”。这个参数在备库上是生效的,但效果完全不同。主库的UNDO_RETENTION可以尽量阻止UNDO被覆盖,但备库的UNDO清除行为依赖日志应用过程重新执行一遍UNDO段的覆盖逻辑,备库本地对UNDO_RETENTION的调整能力非常有限。
我从实践中总结的经验是:如果你在备库上发现有00155报错,先不要急着去调备库的UNDO参数。在19c之前,备库上很多UNDO参数改了根本不生效,因为备库的UNDO段是从主库Redo里重建出来的,不是本地初始化的。这也导致一个很反直觉的结论:备库的UNDO表空间即使你扩得再大,可能也只能缓解一时,不能根治01555。
2.4 一个真实故障的复盘:主库没事备库报错的全过程
有一次我处理过一个案例,可以很好地说明这个问题的隐蔽性。
某个客户的核心系统是Oracle 19.16的ADG双机架构,备库承担着大量报表和查询业务。某天下午,备库上连续有多个报表任务报ORA-01555。从错误日志看,报错的SQL是一条很复杂的多表关联查询,单次执行时间大概在25分钟到40分钟之间。主库上同样的查询没有任何问题。
初步排查时,DBA按照传统经验把备库的UNDO_RETENTION调到了3600秒(此前是900秒),同时给备库的UNDO表空间增加了20GB的数据文件。结果第二天,同样的报错又出现了。
后来我们拉出主库的UNDO使用曲线,发现一个规律:主库上每天下午有一个批量任务,会在短时间内更新一张大表,产生大量UNDO。主库的UNDO_RETENTION虽然设置了900秒,但因为这批事务的量太大,UNDO表空间自动扩展有延迟,导致已经提交的事务在900秒内就被覆盖了。主库自身没有长查询,所以根本不会感知到这个问题。但备库上那些正在跑的报表查询,恰恰需要回看这批被覆盖的UNDO数据,于是01555就在备库上集中爆发了。
这个案例很典型地说明了:备库的ORA-01555本质上往往是主库UNDO压力传导过来的结果,单修备库是治标不治本。
3. 定位备库ORA-01555的排查路径:确认问题边界与UNDO状态
3.1 第一步:确认报错源在备库本地还是主库日志
ADG环境下出现ORA-01555,首先要确认错误到底是在备库本地产生的,还是在主库产生后通过日志同步过来的。
这个判断很简单,查询备库的Alert日志,如果报错信息前面带有“Errors in file”且路径指向备库自身的trace目录,同时报错时间点和应用的报错时间能对上,那基本可以确认是备库本地查询触发的01555。还有一种情况是主库自身在做某些操作时产生01555,这个错误会记录在主库的Alert日志中,但不会导致备库报错——除非主库因为这个错误发生了其他连锁反应。
我建议判断标准就一句话:**备库Alert日志里能直接看到ORA-01555,且伴随有“kdifind: no
