不想让查询把主库拖垮,又怕备库报表半夜报错——这大概是所有做过ADG读写分离的DBA都经历过的纠结。而这里面最常见的拦路虎,就是ORA-01555: snapshot too old,快照过旧。这个错误在单机或主库上出现,大家多少还有排查思路,但一旦发生在ADG备库的只读查询上,很多人会直接懵掉:备库又没有业务写入,哪来的UNDO压力?为什么快照还能过期?
这篇内容,我结合自己处理过的几个生产案例,把ADG只读查询场景下ORA-01555的成因、排查方法和UNDO优化方案一次讲透。无论是刚接手ADG环境的新手,还是被这个问题折磨过的老手,这篇都能给你一些可落地的思路。
1. ORA-01555在ADG备库上的特殊成因
1.1 备库不是”没有UNDO“,而是UNDO不受备库控制
很多DBA的第一反应是:备库是只读的,理论上不会产生新事务,那UNDO为什么要被覆盖?这个问题问到点子上了,也是理解整件事的钥匙。
我们平时在主库上执行一个长查询,Oracle会通过UNDO表空间里的前镜像(undo image)来构建一致性读(CR)块。只要查询开始时的SCN对应的UNDO数据还在,就能读到一致性的历史版本。如果查询需要回看的数据块被修改了太多次,或者UNDO被新事务覆盖了,就会报ORA-01555。
ADG备库的情况就和主库很不一样,它本身不产生UNDO,但它需要从主库接收的redo日志中解析并应用事务。这里必须说清楚的逻辑是:备库上的UNDO数据,本质上是主库UNDO段在redo里的事务映像在备库端的实时回放。备库上的UNDO表空间确实是独立的物理文件,但内容完全由主库的redo驱动,备库自身的进程没有权限去控制UNDO段的保留、清理和覆盖时机。
这意味着什么?意味着你的备库虽然看起来风平浪静没有写入,但备库的UNDO段里正在持续进行着“扩展-覆盖-回收”的循环。这个过程的主控权,在主库上。
继续深入一点,备库应用redo的时候,会模拟主库的UNDO段活动。当主库上某个UNDO段中的事务提交并超过保留期后,主库会把这段UNDO空间标记为可复用,后续新事务会覆盖它。备库端同步执行同样的动作。如果你的备库上刚好有一笔很长的只读查询,其查询SCN对应的UNDO信息在主库端已经被判定为“过期”并被覆盖,那备库的查询在尝试构建CR块时,就会发现找不到需要的前镜像——ORA-01555就此产生。
这里有一个极其重要的推论:备库报ORA-01555,问题的根源几乎总在主库的事务量、UNDO保留策略和备库查询时长之间的三角关系上。单纯在备库上做任何UNDO调整,都是治标不治本,甚至毫无效果。
1.2 什么场景下最容易触发备库快照过旧
从我接触过的生产环境看,ADG备库出现ORA-01555往往有比较明显的场景特征,我列出来供你对号入座:
第一类,也是最高发的场景:备库承担大量报表查询、统计任务、数据抽取。这类查询经常要扫描大表,执行时间动辄几十分钟甚至数小时。如果主库的业务事务又很密集,UNDO段被快速覆盖,那报表查询跑到后半段就很容易撞上快照过旧。我处理过的一个客户案例里,备库上有个统计SQL要跑将近50分钟,主库的UNDO表空间有200G,保留时间设了180分钟,看似绰绰有余,但仍然间歇性报01555。问题恰恰出在UNDO表空间频繁自动扩展又收缩,导致部分UNDO段的状态不够健康。
第二类是主备之间redo传输有延迟的场景,尤其是使用了SYNC模式以外的方式时。备库的redo apply如果跟不上主库的生成速度,备库上的数据SCN会持续滞后。这种滞后会带来一个很奇怪的现象:备库查询明明没有跑太久,但因为数据一直没追上主库,查询看到的SCN区间跨度被拉长了。一旦某个时刻redo追平,UNDO应用瞬间集中释放,刚好和长查询构成竞争,就会触发01555。
第三类是UNDO表空间配置不合理。很多人会认为,备库只读不写,UNDO表空间不需要太大,随便给个10G甚至5G就完事了。这就是备库01555比主库更频繁的隐形推手。因为主库的UNDO可能配置得当,但备库端如果做的是物理备库,它的UNDO表空间大小是继承主库配置的,如果在配置ADG时手动调整过备库UNDO大小,就很容易出现备库UNDO不足、过早覆盖的情况。
还有一类容易被忽略的情况:使用了基于undo_retention的参数调优,但对Oracle 12c之后的PDB架构下UNDO配置变化不了解。如果你用的是多租户架构,UNDO管理模式和相关参数的行为会有差异,这个后面细说。
1.3 ADG查询与主库查询的一致性机制差异
这里再往深挖一层,为什么同一个SQL,应用在主库上不报01555,切到ADG备库就报?这背后的机制差异才是吃透问题的关键。
主库上执行查询时,查询进程直接访问当前的数据块和UNDO段。如果需要老版本的数据,数据库从UNDO段读取前镜像即可。整个过程是“本地实时”的,数据块只要没有被覆盖,就永远可以访问到对应SCN的历史版本。
备库的情况截然不同,它是通过redo日志来回放主库的每一个数据块变更。假设备库查询开始时的SCN是A,查询要读取的数据块在前一秒刚被主库以更高的SCN更新了。备库端为了保证读一致性,需要回到SCN A的状态,这时它就必须去UNDO段找被覆盖前的版本。但是等等——这个过程其实比看上去复杂得多。备库本身的数据块可能已经被redo apply推进到比SCN A更新的状态,而备库端UNDO段中属于SCN A时的数据,需要被保留备查。这里就涉及到一个我称之为“低水位SCN被持续抬高”的问题。
什么是低水位SCN?简单说就是数据库中所有活动事务和保留UNDO所能覆盖到的最早SCN。主库上只要存在一个长事务,或设置了足够的UNDO保留时间,低水位SCN就会被压住不动。但在备库上,UNDO段的保留完全跟随主库的commit和undo_retention策略,不会因为备库上存在长查询就去专门保留哪一段历史版本。备库上的长查询,在Oracle内部并不会有任何机制去“保护”它所需的UNDO不被覆盖。备库是单纯的回放者,不是决策者。
这就是为什么同一个SQL,在主库上优化好UNDO参数后跑得稳稳当当,一放到备库上就时不时给你报个01555——它的UNDO生命周期,压根就不由你的备库查询说了算。理解了这一点,后面的优化方向就顺理成章了:你的核心手段,一定都要围绕如何让主库保留更久的UNDO历史版本,以及如何缩短备库查询所需的历史版本跨度来展开。
先说结论,避免信息过载:
| 关键因素 | 主库查询 | ADG备库查询 |
|---|---|---|
| UNDO来源 | 本地UNDO段,查询可直接读取 | 回放主库redo生成,受主库控制 |
| 低水位SCN | 可由长事务和参数主动保护 | 跟随主库UNDO段的覆盖节奏 |
| 是否受undo_retention保护 | 是 | 延后生效,有滞后性 |
| ORA-01555优化入口 | 本地UNDO管理直接调优 | 优化主库UNDO+优化备库查询时长 |
| 数据块访问 | 直接访问当前块+CU(当前读) | 访问已应用redo的块+CR(一致性读) |
接下来,我会把每一类优化手段和踩坑点完整展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. UNDO参数调优与优化方案
2.1 UNDO表空间大小与undo_retention的配置逻辑
既然已经知道ADG备库的UNDO行为跟着
