ORA-01558 意味着什么
事务回卷是 PostgreSQL 数据库中非常经典的问题,事务 ID 采用 32 位实现,理论上最大支持 4 Billion 个事务(32 位无符号数,xid 最大能够达到 42 亿),但因为 XID 是循环复用的,新老事务相差的上限是 2 Billion,当达到 2 Billion 的上限时会进行事务”回卷“,可能导致新事务被误判早于旧事务,从而破坏 MVCC 机制,极端情况下会导致数据库强制关闭。

注:图片来源于博客园 (lightdb/postgresql 中的事务回卷原理解析及避免 - zhjh256 - 博客园)
Oracle 采用完全不同的设计方式,在事务 ID 方面一向被认为是安全的,但是近期有客户在集群环境中遇到了 ORA-600[4187]或者 ORA-01558 错误,这两个报错表示 undo 表空间中某些回滚段的 wrap#(transaction id)耗尽,最后通过重建 undo 表空间的方式解决。
为什么 Oracle 也会“事务回卷”
探讨解决方案之前,我们先来了解下 Oracle 的 UNDO 实现机制。
Undo 和事务 ID 是 Oracle 管理事务的核心组件,Undo 数据被统一保存在专用的 Undo 表空间中,Undo 表空间被划分为多个 Undo 段,段又是由多个 Extent 组成,以循环的方式写入。每个事务关联到一个 Undo 段,段头包含事务槽(Transaction Slot),记录事务的状态。
Oracle 的事务 ID(XID)由 USN、Slot Number 和 Wrap Number 组成,其中:
USN (Undo Segment Number):事务关联的 Undo 段编号,取值通常在 1~65535 之间; Slot Number:Undo 段头中的事务槽(Transaction Slot)编号,标识事务在段内的具体位置; Wrap Number:事务槽循环使用的计数器,当事务槽被重复使用时,通过 Wrap Number 递增来区分不同周期的事务。
值得一提的是,Oracle 的 Wrap Number 也是 32 位长度。理论上的上限是 42 亿,即使每秒处理 1 万个事务 13.6 万年才能耗尽!但是凡事不能绝对,这不就遇到 ORA-600[4187]错误了吗?!
根据 Bug 19700135 中的记录,当 Wrap 接近最大值时就会触发 ORA-600[4187]错误,其影响范围从 10.2.0.5 到 12.1.0.2,在 12.2.0.1 的基础版本中包含了该补丁。事实上,任何版本都有可能会遭遇 Wrap Number 耗尽的问题,应用补丁 19700135 只是不报 ORA-600[4187]错误,取而代之的是 ORA-01558(All the available transaction id's have been used)。这两个错误本质上是同一个问题。

解决方案
通常认为触发这个问题的原因是由于过高的事务生成率导致,但前面我们也说了,即使是每秒 1 万个事务,42 亿的 XID 也能支持 13.6 万年,而有些客户仅仅建库两年后就遇到这个问题,显然不是单纯的事务生成率能解释的。
通过部署监控脚本,采集问题数据库的 wrap# 变化情况,最终明确是由于两个原因导致异常的增长。
gc_undo_affinity=false
在早些的 Oracle 版本中 DRM 机制存在 Bug,在某些场景中会建议关闭该功能,不同版本中关闭 DRM 的方式有所不同。根据 Oracle 官方文档的说明,_gc_undo_affinity 参数仅适用于 10.2 版本。但可能是历史遗留的原因,有不少客户仍然在 11g 甚至 19c 的数据库中设置了该参数。当设置 _gc_undo_affinity=false 后,为了确保 undo 资源的本地化,会在 wrap# 部分添加一个增量,这会导致 XID 消耗速度比默认状态 (_gc_undo_affinity=true) 下快 15 倍以上。
过低的_rollback_segment_count 设置
自动管理模式下,Undo 段的分配和使用并不总是均匀的,尤其是当在线的 Undo 段数量比较少的情况下,更容易导致某些 Undo 段被过度分配和使用,导致 XID 消耗速度增加。
通过以上的分析,我们很容易总结出相应的规避方案:还原 _gc_undo_affinity 参数为默认值,使用官方建议的方法来禁用 DRM 功能;设置合适的 _rollback_segment_count 值,避免 Undo 的使用集中在少数几个段上,从而降低 XID 的消耗速度。
除了参数上的调整之外,对 wrap# 的使用情况进行监控也是及时发现问题的重要手段,相关的脚本在 Oracle MOS 中也能找到。限于篇幅的原因这里就不贴了,有需要的朋友可以在后台留言,我把脚本私信给大家。
附录:相关 Bug 及参考文档
Bug 37448444 : ERROR ORA-01558 REPORTED ONLY ON ONE DATABASE WHEN _GC_UNDO_AFFINITY=FALSE IS SET FOR MULTIPLE DATABASES Bug 36788979 : UNDO SEGMENTS ARE NOT USED EVENLY Bug 35903024 : CN: ORA-1558 HAPPENED JUST AFTER CREATING THE DATABASE 2 YEARS ORA-1558 Happened On RAC Database (Doc ID 3033808.1) OERR: ORA-1558 "out of transaction ID's in rollback segment %s" Reference Note (Doc ID 18957.1) Bug 19700135 : ORA-600 [4187] WHEN WRAP# IS CLOSE TO KSQNMAXVAL
大家在数据库运维过程中还遇到哪些问题呢,欢迎关注公众号,留言讨论。