ARTICLE · 998833
基于源码的 openGauss UStore Update 机制分析
打个广告:www.bic-qa.com是老白团队开发的一个数据库公共智能体,可以支持大多数国产数据库,希望各位读者给捧个场。
1. UStore 本地更新的准确含义
UStore 与继承 PostgreSQL heap 的 AStore 并存。AStore 的 UPDATE 会创建新的物理 heap tuple;新版本可以位于原页,也可以位于其他页,满足 HOT 条件时可避免部分索引维护,但仍然会获得新的 tuple 位置。UStore 的 in-place update 则继续使用原 tuple slot,用新行映像覆盖当前数据区,并把旧版本保存在独立 Undo 中。
判断维度 | UStore in-place update 的实际行为 |
是否产生新的逻辑版本 | 产生。UPDATE 前后的值仍是两个可见性不同的逻辑版本。 |
是否分配新的表页 tuple slot | 不分配;当前版本继续占用原行位置。 |
旧版本保存在哪里 | 保存在 Undo 中,必要时由当前行映像与 Undo 信息重构。 |
行定位是否改变 | 本地更新路径通常保持原 TID/slot;link update 则产生新物理位置。 |
是否天然避免索引维护 | 否。索引键、表达式依赖列或约束相关列变化时仍需维护索引。 |
因此,“不产生新版本”应修正为“不在表数据页上产生新的物理行版本”。同样,“缩短版本链”应理解为避免数据页上的 tuple-to-tuple 更新链,而不是消除 Undo 版本链。
2. TD 槽与 Undo:算法的版本枢纽
2.1 三层定位关系
UHeapDiskTuple 行头中的 td_id 是页头 TD 数组的槽号。TD 槽记录最后修改相关行的事务标识以及 Undo 入口。当前行需要回滚或查询需要较旧版本时,系统从行头进入 TD,再根据 Undo 记录保存的旧行头、旧事务关联和旧数据继续回溯。
版本入口替换:更新不是简单地“把 td_id 改成本事务槽”。完整动作是:将当前物理行的版本入口切换到本事务 TD,同时把被替换的旧行头、旧 td_id/旧 TD 关联以及旧数据恢复信息写入 Undo。这样当前版本和前驱版本才能闭合成链。 |
2.2 与 Oracle ITL 的相似与差异
Oracle 每个数据块头包含 ITL(Interested Transaction List)。行锁字节关联到 ITL,ITL 再关联事务表和 Undo 信息。Oracle 读取者遇到更新后的块时,可利用 Undo 构造目标 SCN 的一致性读块。UStore 的 TD 与 Oracle ITL 在“块级事务槽”这一思想上相似,但字段组织、事务状态表达、Undo 定位以及一致性读的实现粒度并不相同,不能将 TD 直接等同为 Oracle ITL 的复制。
3. UHeapUpdate 的核心流程

图 1UStore UPDATE 的主路径与分支
3.1 可见性、锁与并发冲突
更新首先定位旧行并执行 UHeapTupleSatisfiesUpdate,判断该版本对当前快照是否可更新。如果行正在被其他事务修改,则根据等待策略进入 UHeapWait 或返回并发结果。只有在锁和可见性状态稳定后,后续的 tuple 长度、TOAST 和页空间判断才有意义,因为等待期间行版本或页面布局可能已经变化。
3.2 预留 TD 槽
UHeapPageReserveTransactionSlot 在旧页为当前事务预留或复用 TD 槽。若最终走跨页 link update,则旧页与新页分别需要事务槽,相关双页预留逻辑属于非原地更新路径。严格意义上的 in-place update 只修改旧页,不存在“跨页原地更新”。
3.3 构造 Undo
UHeapPrepareUndoUpdate 为更新准备恢复信息。Undo 至少要能够恢复旧行头、旧事务关联和被覆盖的数据。对于适合压缩的原地更新,系统会利用共同前缀、共同后缀及差异区间生成紧凑表示,部分路径使用 XOR 信息。它不是只保存几个变化字节;NULL bitmap、tuple header、长度和偏移等恢复元数据同样重要。
3.4 临界区、页面修改与 WAL
进入临界区后,系统设置新 TD 槽、更新行标志和修改事务标识,再调用 PutInplaceUpdateTuple 覆盖当前 tuple slot;若原地条件在最终写入阶段不再成立,可退化到同页 link update。随后 UHeapPageSetUndo 将 TD 写成本事务及其 Undo 入口,WAL 同时保护数据页和版本关系的恢复。
4. 原地更新的可行性判定
条件 | 作用 | 失败后的结果 |
不需要 TOAST | 避免把主表覆盖与 TOAST 表插删、外部值生命周期组合进本地更新。 | 进入 TOAST/link update 路径。 |
调用方允许 allow_inplace_update | 执行器或上层语义可以禁止本地更新。 | 使用 link update。 |
新长度不大于旧长度 | 原位置天然容纳新行,无须扩展连续空间。 | 若新行更大,继续检查页空间。 |
可整理出净增长量 | prune/compact 后存在足够连续空间。 | 无法获得空间时 link update。 |
行尾不越过页边界 | 原 tuple slot 扩展仍位于本数据页。 | 禁止原地更新。 |
4.1 TOAST 限制应怎样理解
在该源码基线中,需要 TOAST 的更新被排除在 in-place 路径之外,这是实现的安全边界,不是数据库理论上的必然限制。理论上可以先创建外部值,再原地更新主行 locator,并通过 Undo/WAL 管理外部对象;Oracle LOB 即采用另一套成熟的外部对象管理体系。UStore 当前选择了更保守的组合,代价是宽行和大变长字段更新更容易退化。
4.2 页内整理并非免费
当新行略大于旧行时,为争取本地更新而执行 prune/compact 可能移动页内数据、延长 buffer exclusive lock 持有时间并增加 WAL。若整理后仍失败,系统既支付了整理成本,又要执行 link update。因此,应同时观测“in-place 成功率”和“为尝试 in-place 付出的页整理成本”,不能只统计最终分支。
5. Undo 压缩、一致性读与恢复
5.1 差异 Undo 的收益与代价
差异/XOR Undo 对“大行中少量字节变化”最有价值:它减少 Undo 空间、WAL 负载和缓存占用。但更新时需要比较新旧数据,回滚和历史读取时还要执行反向重构。对于很短的行、几乎整行变化或 CPU 已饱和场景,压缩计算未必带来净收益。
5.2 正常一致性读
Undo 不仅服务显式 ROLLBACK。查询快照早于当前修改时,同样需要沿 TD/Undo 找到合适版本。若同一行被高频更新,或者长查询需要跨越较长时间窗口,Undo 链遍历、事务状态判断和 Undo 页缓存未命中可能成为延迟来源。
5.3 崩溃恢复
恢复过程应区分 redo roll-forward 与未提交事务回滚。WAL 先重放已记录的数据页和 Undo 相关变化,随后利用 Undo 消除未提交事务的效果。独立 Undo 为快速回滚和历史读取提供基础,但 RTO 仍取决于日志重放量、脏页规模、未完成事务数量、Undo 链长度和恢复并行度,不能由 in-place update 单独保证。
6. In-place update 临界写路径的源码拆解
前面的流程说明了系统“做什么”,本节进一步解释临界区内“以什么顺序做”。原地覆盖已经破坏数据页中的旧值,因此 tuple header、TD 槽、Undo 入口和 WAL 之间必须满足严格的先后关系:任何一个环节若只完成一半,都可能使恢复过程既找不到旧版本,也无法判断当前行属于哪个事务。
6.1 更新前必须保留的旧状态
进入物理写入前,系统首先从旧 tuple header 和旧 TD 槽中取得前驱版本信息。需要保留的不只是用户列值,还包括旧 td_id、旧标志位、旧修改事务、旧 tuple 长度以及指向更早 Undo 的关系。它们共同组成回滚后的完整行状态。若只保存列差异而遗漏旧头部,回滚后即使数据内容正确,版本链和锁状态也可能已经失真。
6.2 行头为何切换到本事务 TD
当前数据页中的 tuple 始终代表最新物理映像,因此它的 td_id 必须指向最后修改该映像的事务。UHeapTupleHeaderSetTDSlot 将入口切到 oldtupNewTransSlot,UHEAP_INPLACE_UPDATED 表示该版本由本地覆盖产生,相关锁标志和 modified xid 则支持并发判断。旧入口并未丢失,而是被写入本次 Undo,成为继续回溯的前驱。
6.3 PutInplaceUpdateTuple 的物理含义
PutInplaceUpdateTuple 保留原 row pointer/tuple slot 的身份,在其指向的数据区写入新 tuple。新行不大于旧行时,覆盖后的多余空间可转化为页内潜在空闲空间;新行更大时,只有在整理后存在连续增量空间且行尾不越界的情况下才能扩大原记录。这里所谓“原地”是行位置不变,并不保证页内其他行绝对不移动——前置 prune/compact 可能已经重排页内数据。
6.4 TD 槽落页与版本链闭合
新数据写入后,UHeapPageSetUndo 将本事务 fxid 和当前 Undo 指针写入预留 TD 槽,形成 tuple→TD→Undo 的新入口。随后事务级 Undo 链尾也要更新,使回滚器能够按照事务操作的逆序找到本次变化。行级版本链和事务级 Undo 链是两个不同视角:前者回答“这行以前是什么”,后者回答“这个事务需要撤销哪些动作”。
6.5 WAL 保护的不是单一 memcpy
本地更新不能只记录新值覆盖。Redo 至少需要重建数据页上的 tuple、row pointer/长度、TD 槽及相关页头状态;Undo 记录自身的持久化变化也要受到日志保护。实例恢复先通过 WAL 重建崩溃前已经发生的物理变化,再通过 Undo 清除未提交事务。由此可见,in-place update 节省的是新 tuple 和部分空间操作,并不等于取消日志。
7. Link update:本地更新算法的边界路径
理解 in-place update 不能只看成功分支。UHeapUpdate 是一个同时承载本地更新和 link update 的统一入口;只有把退化路径放在一起,才能判断原地优化是否真正降低了成本。
7.1 同页 link update
当旧页仍能容纳一个新 tuple,但原 row pointer 后方没有连续扩展空间时,系统可能在同一页分配新的 row pointer/tuple 位置。旧行被标记为已更新,新行成为当前版本,Undo 或链接信息维持两者的逻辑关系。它不需要跨页取 buffer,却仍会消耗新的 tuple slot 和页内空间。
7.2 跨页 link update
旧页无法容纳新行时,需要选择新页并分别为旧页、新页预留 TD 槽。此时必须处理双 buffer 的锁顺序、双页 Undo、目标行位置以及 WAL 原子性。跨页路径的开销显著高于本地覆盖,也是共享缓存集群中更容易产生多页面全局协调的分支。
7.3 TOAST 退化
若新旧元组存在外部字段或新行达到 TOAST 处理条件,当前实现停止尝试 in-place update。主行中的变长值可能被替换为外部指针,同时 TOAST 表及其索引发生插入、删除或更新。此时业务上的一次 UPDATE 已经扩展为多个关系、多个页面和多条日志记录,不能再用主表 tuple 是否原地覆盖衡量写放大。
7.4 退化成本模型
对一次可能增长的 UPDATE,可将其成本概括为:可见性与锁成本+TD 预留成本+页整理尝试成本+Undo/WAL 成本+最终写路径成本。若页整理成功,本地更新可能抵消前置开销;若整理失败再执行 link update,则前置尝试成为额外成本。因此工程上应记录“首次直接原地”“整理后原地”“同页 link”“跨页 link”和“TOAST link”五类结果,而不是一个简单的 in-place 布尔值。
8. 并发控制、可见性与 Undo 生命周期
8.1 读写并发
写事务覆盖当前行并不会阻塞普通快照读。读取者根据自身快照判断当前 TD 所属事务是否可见;若当前版本过新,则读取 Undo 并反向重构。因而 in-place update 的读写解耦依赖 Undo 可用性,而不是依赖数据页上保留旧 tuple。
8.2 写写冲突
两个事务更新同一行时仍必须串行化。后到事务需要等待、检测对方事务状态,并在等待结束后重新检查行版本。TD 槽是版本入口,不是绕过行级 write-write 冲突的机制。高频热点行即使全部成功原地更新,也不会随并发线程线性扩展。
8.3 长事务与旧快照
Undo 回收必须尊重最老活动快照和事务可见性水位。长查询会延长旧 Undo 的生命周期,使 Undo 空间增长、链深度增加并降低缓存命中。反过来,若 Undo 在仍可能被读取时过早回收,系统就无法重构一致版本。因此 Undo retention 是性能、空间与历史可见性之间的平衡,而不是越短越好。
8.4 TD 槽复用的安全条件
页上的 TD 数量有限。槽复用前必须确认旧事务状态已经稳定,且经该槽进入的历史版本仍可通过 Undo 正确解释。高并发短事务修改同一页时,即使各自更新不同记录,也可能在 TD 查找、扩展和复用上形成页级竞争。这是 UStore 将逐行事务字段压缩到页级结构后需要承担的共享元数据代价。
8.5 UStore 算法最适合的负载
综合源码路径,收益最大的场景是:定长或轻微缩短的行、更新非索引列、不触发 TOAST、热点分散在多个数据页、快照持续时间较短。相反,变长列持续增长、同页高并发、热点行、索引键频繁变化、宽行 TOAST 和长快照,会依次削弱原地覆盖带来的空间收益。
9. 同事务重复更新、锁状态与版本合并
同一个事务可能多次更新同一行,例如一条存储过程先修改状态,再补充时间戳。此时不能机械地把每次 UPDATE 都当成彼此独立的事务版本:可见性判断需要识别当前行是否已经由本事务修改,锁状态需要避免把本事务误认为冲突者,Undo 又必须保证 ROLLBACK TO SAVEPOINT 和整事务回滚都能恢复到正确层次。
9.1 self-update 的识别
UHeapTupleSatisfiesUpdate 返回的不只是“可见/不可见”,还要区分由当前命令、当前事务或其他事务造成的修改。当前事务再次命中本行时,上层参数 allow_update_self、命令 ID 和行头状态共同决定允许继续更新、报告 self-modified,还是沿已有更新关系找到最新版本。该分支直接影响触发器、重复键更新和复杂 DML 的正确性。
9.2 锁标志与数据更新标志不能混为一谈
行可能只被 SELECT FOR UPDATE/SHARE 锁定,也可能已经发生数据覆盖。UHEAP_XID_EXCL_LOCK、UHEAP_INPLACE_UPDATED 等标志承担不同语义:前者描述锁模式或修改者排他关系,后者描述当前版本的形成方式。Undo 必须能够恢复二者,否则回滚一个锁操作可能错误地回滚数据,或回滚数据后留下虚假的行锁。
9.3 Savepoint 对 Undo 粒度的要求
事务内多次更新同一行时,最终数据页只保存最后一次结果,但 Undo 链上必须保留足够的操作边界。回滚到保存点只撤销保存点之后的更新,完整回滚则继续沿链恢复事务开始前的版本。差异 Undo 可以压缩每一步,但不能把多个逻辑操作无条件折叠为一个最终前像。
9.4 更新链追随与并发者重新检查
等待其他事务结束后,原先定位的 tuple 可能已不再是最新版本。更新者必须重新检查可见性,并在 link update 情况下追随到新位置。in-place update 保持原 TID,可减少这类物理追随,但仍要确认等待期间 TD 和版本状态是否变化。原 TID 不变改善了定位稳定性,却没有取消并发重检。
10. 页面空间不变量与原地扩展算法
新行不大于旧行时,本地覆盖相对直接;真正复杂的是“新行略大但仍希望保持原 slot”的情况。此时算法必须同时维护 row pointer 数组、TD 数组、tuple 数据区和页头上下界,保证任何行都不与元数据区重叠。
10.1 净空闲与连续空闲的区别
页面总空闲字节数足够,并不表示旧行尾部存在可连续扩展的空间。已删除元组、缩短后的空洞和 line pointer 间隙可能使空闲空间碎片化。UHeapPagePruneOpt 的作用不仅是回收逻辑死亡版本,还要通过整理把分散空间转化为可用于本次增长的连续区间。
10.2 为什么必须排除 oldOffnum
为本行扩展空间时,清理逻辑必须保护正在更新的 oldOffnum,不能把它作为普通死亡元组回收或改变其语义身份。即使整理移动了物理字节,row pointer 仍要稳定指向整理后的旧行,随后 PutInplaceUpdateTuple 才能基于正确 offset 覆盖。
10.3 页边界检查是最终物理门禁
整理完成后仍需验证 RowPtrGetOffset(lp)+newtupsize 不超过 BLCKSZ,并且不会侵入 row pointer/TD 元数据区域。这个检查不能只依赖“空闲量大于净增量”,因为错误的连续区间位置同样可能越界。它是逻辑空间估算之后的物理安全门禁。
10.4 potential freespace 的意义
更新缩短行或 link update 留下旧版本后,页面可能产生未来可回收空间。potential freespace 不是当前立即可分配的连续空间,而是向后续 prune、空闲空间管理和页面选择提供提示。若把潜在空间直接等同于可用空间,会高估 in-place 成功率并导致重复整理。
10.5 Fillfactor/预留空间仍然重要
in-place update 并不能消除页面填充策略。对于变长字段经常增长的表,建表或装载阶段保留合理空闲量,可以显著减少 prune 尝试和 link update。若页面长期接近满载,任何小幅增长都会把本地更新从低成本覆盖变成页整理甚至跨页写入。
11. Undo、WAL 与写放大的成本模型
UStore 的优势经常被概括为“少写一份新 tuple”,但完整成本至少包括数据页、Undo、WAL、索引和可能的 TOAST。只有把这些部分分开,才能解释为什么某些场景 in-place 成功率很高,吞吐却没有同比提升。
11.1 数据页写入量
本地更新写入新行数据、行头、row pointer 长度和 TD 槽;若发生页整理,还会移动本次更新之外的其他 tuple。因而数据页脏字节可能大于业务列变化字节。数据库通常以页为 I/O 和缓存一致性单位,即使只改几个字节,最终落盘与跨节点传输也可能以整页计。
11.2 Undo 写入量
Undo 由固定元数据和可变恢复数据组成。短行更新时,固定头部占比可能较高;大行小改动时,前后缀裁剪和 XOR 才能明显降低空间。评价压缩效果应同时统计原始旧 tuple 大小、实际 Undo payload、Undo record 总长度和重构 CPU。
11.3 WAL 写入量
WAL 不仅描述用户列变化,还要保护 TD、row pointer、页整理结果和 Undo 相关修改。首次修改检查点后的页面还可能产生 full-page image 等额外日志。因此“Undo 变小”不必然按相同比例降低 WAL,必须以实际日志字节数和每事务 flush 次数衡量。
11.4 索引与 TOAST 写放大
索引键变化会产生独立的 B-tree 修改;需要 TOAST 时还会修改外部表和索引。这些成本与主表是否原地更新相对独立。若表有多个二级索引,主表节省的一个 tuple 可能只占总写入的一小部分。
11.5 可用于压测的分解公式
可将一次 UPDATE 的近似写放大定义为 WA=(数据页脏写字节+Undo 字节+WAL 字节+索引写字节+TOAST 写字节)/业务实际变化字节。集群中再加入页面网络传输字节,得到 Cluster-WA。这个模型不追求精确计费,而是用于定位收益究竟来自哪一层、瓶颈又被转移到哪一层。
12. 以 Oracle 为参照理解 UStore
Oracle 的常规 UPDATE 通常也是“原数据块保留当前值、Undo 保存旧值”的物理原地更新。数据块 ITL 描述影响该块的事务,行锁字节关联 ITL;Redo 同时保护数据块修改和 Undo 块修改。读取者需要旧版本时,Oracle根据查询 SCN和ITL/Undo 构造 consistent-read block。
比较项 | openGauss UStore | Oracle |
页内事务槽 | TD slot,行头 td_id 指向页头 TD。 | ITL entry,行锁字节关联 ITL。 |
历史版本 | 独立 Undo;tuple→TD→Undo 路径清晰。 | Undo segment;事务表和 UBA/Undo 链配合。 |
一致性读倾向 | 以 tuple 版本重构路径为核心。 | 以目标 SCN 的 CR block 构造和复用为核心。 |
行增长 | 可尝试页内 prune/compact;失败后 link update。 | 利用块内空间;不足时可能 row migration/chaining。 |
大字段 | TOAST;当前本地更新路径通常排除需 TOAST 的行。 | LOB locator 与 LOB segment,SecureFile 体系更成熟。 |
Undo 表示 | 可采用前后缀裁剪和 XOR 差异编码。 | 内部 change vector/Undo 格式长期演进,细节非公开接口。 |
工程成熟度 | 较新的存储引擎,特性和极端场景仍需验证。 | 经历长期大型 OLTP 与 RAC 工作负载优化。 |
老白观点:UStore 在数据页上少写一个物理 tuple,并不能直接证明其 UPDATE 比 Oracle 更便宜。事务槽竞争、Undo 生成与重构、索引维护、页面整理、日志量和集群传页共同决定最终成本。 |
13. UStore 单机性能上可能存在的弱点
13.1 TD 槽与页头热点
大量短事务并发修改同一页时,TD 查找、预留、扩展或复用会集中修改页头。即使不同事务更新不同的行,它们仍可能竞争同一个 buffer content lock 和页头 cache line。Oracle 也存在 ITL waits,但 Oracle 对 ITL 扩展、事务槽复用和块清理拥有更长的工程优化历史。
13.2 长 Undo 链与随机访问
热点行连续更新会形成较长 Undo 链。旧快照读取需要重复判断事务状态并访问多个 Undo 记录;当 Undo 不在内存中时,可能转化为随机 I/O。Oracle 也会遇到长回滚链,但其 CR block 复用可能在扫描同一块多行时摊薄一部分成本。
13.3 页整理的失败成本
行增长型更新可能先触发 page prune/compact,再因连续空间仍不足而退化。此类“先整理、后失败”的路径可能导致长尾延迟,尤其当数据页已经接近满载、PCTFREE 等价空间策略不合理或变长列频繁增长时。
13.4 TOAST 与宽行更新
需要 TOAST 的行不能享受当前本地更新路径,可能同时修改主表、TOAST 表和 TOAST 索引。与 Oracle SecureFile LOB 相比,TOAST 在局部大对象更新、缓存策略和生命周期管理方面通常不是 UStore 的优势区。
13.5 索引仍可能主导成本
若索引键变化,heap 行原地更新仍伴随索引删除/插入、唯一性检查和 B-tree 页分裂。对于主键递增、低选择性热点键或大量二级索引表,索引叶块可能比 UStore 数据页更早成为瓶颈。
14. oGRAC/资源池化架构中的额外性能隐患
openGauss 共享存储/资源池化架构使用 DSS 管理共享存储,并由 DMS 通过 TCP 或 RDMA 在节点间交换热数据页、维持实时一致性。这与 Oracle RAC Cache Fusion 在目标上相似:各实例具有本地 buffer cache,但对共享数据块的访问必须服从全局页面状态和跨节点一致性协议。本文将这类部署统称为 oGRAC 场景,但具体产品版本、节点角色和读写能力应以实际部署为准。

图 2UStore 与共享缓存集群可能形成的性能耦合
14.1 数据页写权 ping-pong
in-place update 必须获得旧数据页的可写权,并修改 tuple、TD 槽和页头信息。如果节点 A、B 交替更新同一数据块中的行,即使不是同一行,也可能反复转移整个页面的全局主控权。此时节省一次 tuple 分配的收益,可能远小于一次跨节点页面往返的成本。
老白观点:UStore 的 TD 位于页头。不同节点更新同一页中的不同记录,逻辑上没有行冲突,但物理上仍可能因页面写权和页头 TD 修改产生“伪共享”。这与 Oracle RAC 热块/Cache Fusion ping-pong 类似,但 UStore TD 的分配与复用可能增加额外页头写频率。 |
14.2 TD 槽竞争被集群网络放大
单机中 TD 槽不足表现为页内锁和事务状态处理;在共享缓存集群中,申请或复用 TD 前可能还需要取得页面独占状态。热点页上大量短事务会把“TD 管理+页面权属”组合成一次分布式临界路径。若 DMS 使用 TCP,网络往返和调度抖动可能形成明显尾延迟;RDMA 可降低传输成本,但不会消除页面所有权冲突。
14.3 一致性读与 Undo 的跨节点依赖
节点 B 收到当前数据页后,如果其查询快照需要较旧版本,还要根据 TD/Undo 还原历史行。这里需要确认三个实现问题:Undo 页是否可由任意节点直接从共享存储读取;相关事务状态是否已全局可见;历史版本重构是在页面源节点完成后传输 CR 页,还是在请求节点本地完成。不同选择会导致远程页传输、Undo I/O 和 CPU 消耗的不同组合。
若请求节点既要取得当前页,又要额外访问远端或共享存储中的 Undo/事务状态,一次逻辑读可能被放大成多次网络或存储访问。Oracle RAC 的 CR block shipping、past image 与 Undo 应用路径经过长期优化;oGRAC 必须通过观测证明其是否能实现相近的 CR 构造复用和远程依赖收敛。
14.4 热点行更新与全局锁队列
不同节点更新同一行时,行级 write-write 冲突本身不可并行。集群模式还叠加页面权属、行锁等待、事务状态传播和唤醒。高频账户余额、序列控制表、任务状态表等热点行可能出现吞吐不随节点数增长,反而因全局往返下降。
14.5 页内整理扩大独占持有时间
新行增长时,UStore 可能在持有可写页面期间执行 prune/compact。整理动作移动页内数据并延长独占持有时间。在此期间,其他节点对该页的读取或写入更容易排队;如果整理最终失败并退化为 link update,还可能继续申请目标页的全局状态,形成双页协同和更长临界路径。
14.6 Undo 回收水位和长快照
集群的最老可见快照需要在全局范围内考虑。某个节点上的长查询可能阻止其他节点产生的 Undo 被回收,扩大 Undo 空间和历史链长度。节点故障、reform 或事务状态重建期间,TD/Undo 的可达性与回收安全水位也可能影响恢复时间和业务长尾。
14.7 索引全局热点可能掩盖 UStore 收益
即使表行成功原地更新,修改索引键仍会争用索引叶块。递增主键右侧叶块、低基数状态索引、唯一索引检查和频繁页分裂,都可能触发跨节点索引块 ping-pong。在 RAC/oGRAC 对比中,应分别统计数据块和索引块传输,否则容易把索引热点误判为 UStore 更新算法问题。
15. Oracle RAC 与 oGRAC 风险对照
风险维度 | Oracle RAC | oGRAC + UStore 的关注点 |
热点块传输 | Cache Fusion 中常见 current/CR block 传输与 gc waits。 | DMS 页面交换延迟、写权迁移次数、TCP/RDMA 差异。 |
块内事务槽 | ITL 不足可能产生 ITL contention。 | TD 预留/复用与页面独占权耦合,需确认是否出现更高页头修改率。 |
一致性读 | CR clone、Undo 应用和块传输机制成熟。 | 需要量化 tuple/页级重构、Undo 访问位置及 CR 页复用能力。 |
热点行 | 行锁+全局块管理,扩节点不能消除同一行串行化。 | 同样受限,并可能叠加 TD/事务状态传播。 |
行增长 | 块内空间管理或 row migration。 | prune/compact 后可能 link update,独占页时间可能更长。 |
大字段 | SecureFile LOB 与 RAC 有成熟路径。 | TOAST 跨对象更新及本地更新退化应重点测试。 |
故障重构 | 成熟的 GES/GCS reconfiguration 与实例恢复。 | DMS reform、Undo/TD 可达性和未完成事务回滚需验证。 |
Oracle RAC 也并非天然规避上述问题。热点块、全局缓存传输和 ITL 竞争都是经典 RAC 性能问题。真正差距更可能来自协议成熟度、CR 镜像复用(有效降低节点间CR数据传输)、诊断可观测性、故障重构和大量边界场景的工程优化,而不是两者是否都支持“原地更新”。