做RAC的DBA久了会养成一个习惯:看到任何和RAC相关的性能问题,第一反应不是急着跑脚本、看AWR,而是先问自己——这个等待是走GCS还是GES?换句话说,问题的根子是出在PCM资源上,还是非PCM资源上?这个分类意识,比背一百个等待事件名称都管用。
Oracle RAC的“内存融合技术”,就是大家常说的Cache Fusion,它之所以能在多个实例之间像一份大缓存一样工作,是因为底层把需要全局协调的资源分成了PCM资源和非PCM资源两大类。PCM管的是数据块本体,非PCM管的是锁、字典缓存、库缓存这些“元秩序”。两类资源像两个声部,在私网上协同演奏,才形成了集群缓存一致性的交响效果。
这篇文章不打算从头讲一遍RAC体系架构,也不打算罗列命令,而是专门把PCM和非PCM资源这两条主线拎出来,从原理拆到实操,再结合实际场景聊聊怎么用这套思路做问题定位。刚接触RAC不久、书本看得云里雾里的DBA适用,被gc等待事件和library cache lock折磨过的运维老兵也一样适用。
1. 先想清楚:内存融合到底融合的是什么
很多人以为RAC里的缓存融合就是把多个实例的SGA拼成一个大内存池,这是最常见的误解。实际上每个实例的buffer cache依旧是物理独立的,只是通过某种协议让这些独立的缓存对外表现为一个“逻辑上一致”的整体。数据块不一定只存在一个实例里,多个实例可能同时持有同一个数据块的不同副本,但它们彼此都清楚谁手里的版本最新、谁可以安全读取、哪个副本可以用来构造一致性读。
1.1 从磁盘Ping到高速私网传输
在Oracle 9i之前的OPS(Oracle Parallel Server)时代,多个实例访问同一个数据块,如果块不在本地,必须先让持有块的实例把脏块写回磁盘,请求方再从磁盘读进自己的buffer cache。这个动作在当时叫“磁盘Ping(Disk Ping)”,代价极其昂贵——一次跨实例共享访问至少产生两次磁盘写和一次磁盘读。OPS也因此只能在极少数几乎不冲突的场景里跑出扩展性,更多时候性能和单实例相比反而是灾难。
Cache Fusion彻底改变了路径:数据块不再需要先落盘再被另一个实例读取,而是直接通过专用的高速私有网络,从持有该块的实例buffer cache送到请求实例的buffer cache。这个过程叫“块传输”,在等待事件里就是gc cr request、gc current request这一族。注意,融合的是缓存中的块,不是磁盘文件本身。Disk I/O并没有被消灭,只是被极大延后:只有到了检查点、实例恢复、空间管理等时机,脏块才会被批量写入磁盘。
1.2 让多实例缓存保持一致性的核心矛盾
有了块传输还不够,Oracle必须回答三个问题:这个数据块现在在哪个实例手上?那个实例手上持有的是最新版本吗?如果我要修改它,需不需要先通知别人?
解决这三个问题的机制就是GRD(Global Resource Directory,全局资源目录)。GRD是一份分布式目录,不集中存放在单独节点,而是按资源散落在各个实例上。每个资源有一个“主节点”(Resource Master),负责记录该资源的持有者、请求者、锁模式等信息。谁想访问一个数据块,先向这个块的资源主节点问路,主节点根据目录内容决定是直接授权,还是转交持块实例去传送。
1.3 为什么要把资源人为分成PCM和非PCM
RAC里需要全局协调的实体远远不止数据块。表结构定义、数据库字典缓存、库缓存中的SQL游标、DML时的表锁,这些都需要实例间一致协调。但它们和数据块有一个本质区别:数据块是访问最频繁、最追求低延迟、数量最庞大的资源;而锁和元数据更多是短促、低频、逻辑复杂的排队型资源。面向不同特性设计两套协调机制,性能和可靠性才能兼顾。
于是Oracle把资源拆成两大类:
- PCM资源(Parallel Cache Management Resource):协调数据文件中的数据块,归GCS(Global Cache Service)管理,对应Cache Fusion核心流程。
- 非PCM资源(Non-PCM Resource):协调除数据块外的其他全局资源,归GES(Global Enqueue Service)管理,常见的是各种enqueue、library cache lock、row cache lock。
用一个生活化类比:PCM资源像高速公路上的车流,讲究的是快速通过、少停车、低延迟;非PCM资源像路口红绿灯系统,讲究的是规则清晰、公平排队、按序放行。两者共同构成立体交通,单看哪一边都无法理解RAC的全貌。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. PCM资源深度拆解:数据块的“通行证”机制
PCM资源是整个内存融合技术的核心,也是最容易让人卡住概念的地方。我见过不少朋友把PCM资源直接理解成“数据块本身”,严格说这并不准确。PCM资源更像挂在每个数据块上的“通行证”,通行证记录了当前允许谁持有、允许哪种方式的访问。
2.1 PCM资源到底映射了什么
每个PCM资源通过哈希算法与数据文件中的block映射。Oracle内部会依据file number和block number计算出一个资源ID,整张表的block被分布到一组数量有限的PCM资源上。资源数量和buffer cache大小、block size都有关系,具体数值由内部参数决定,不需要DBA手工设置。也就是说,一个PCM资源名下可能挂着一组数据块,而不是每个block独占一个资源。
这个设计带来的直接影响是:如果两个毫无逻辑关联的block哈希到同一个PCM资源上,当其中一个block需要跨实例传输时,另一个block的访问也会受到同一个资源锁的影响,在AWR里表现出来就是无规律的gc buffer busy。这种情况在资源数量配置偏少、buffer cache特别大时更容易出现,排查起来也很麻烦。
在GRD里,PCM资源需要记录的信息至少包括:当前master节点是谁、哪些实例以什么模式持有该资源、持块实例上buffer的状态是当前版本还是过去版本。这套信息决定了后续所有块传输和锁转换动作。
2.2 PCM资源的锁模式与缓冲状态
PCM锁的模式从概念上可以简化成三种:NULL、S(共享)、X(独占)。NULL表示这个实例对该资源没有访问权;S模式允许多个实例同时持有并读取块;X模式只允许一个实例持有,并且只有X模式持有者可以修改数据块。
当一个实例需要读取某个block时,通常需要获取S模式的PCM锁;当需要修改block时,必须获取X模式的PCM锁。锁模式的转换需要经过资源master统一调度,这也就是为什么跨实例访问数据块时会有“消息往返”,而不是直接本地操作。
另一个与PCM紧密相关的概念是buffer cache里block的状态,常见的有:
- xcur(exclusive current):节点持有PCM X锁,拥有块的最新版本,允许修改。
- scur(shared current):节点持有PCM S锁,可以读取块的当前版本。
- cr(consistent read):节点上保存的是通过undo构造出来的一致性读版本。
- pi(past image):节点曾经持有X锁,后来把当前版本传给其他实例,自己保留了一个旧版本,用于实例恢复或延迟写盘。
特别注意pi状态。当一个数据块从节点A传到节点B去修改时,A不会立刻把自己buffer cache里的旧拷贝扔掉,而是标记成past image。这个past image不是垃圾,它承担着重要职责:如果B实例在写回磁盘前宕机,A的past image还可以配合redo参与恢复,避免数据丢失。很多DBA看到AWR里gc cr block丢失之类的统计偏高,其实很大程度上就与PI处理策略有关。
2.3 一次gc cr request的完整链路
用一个最典型的跨实例CR读请求来串一遍PCM流程。假设实例A上有一个会话要查询某个block的一致性读版本:
第一步,实例A先在本地buffer cache查找。没找到的话,进入GCS调用流程。
第二步,A根据block的file号和block号计算PCM资源,判断出这个资源的master在哪个实例。多数情况master不在本机,A需要向master发送一个CR请求消息。
第三步,master查看GRD,发现该block的current版本在实例B上,并且B持有X模式PCM锁。于是master通知B:A需要这个块的CR版本。
第四步,B在自己buffer cache中找到current版本的block,结合undo构造出符合查询SCN的CR镜像,通过私网直接发给A。这一步就是常说的“块服务”。B手上的current块可能继续保留,也可能视策略被降级,但master会更新GRD中的记录。
第五步,A收到CR块,完成读操作。如果A只是要CR读,通常无需把PCM锁从B手里抢过来,B仍然保留X锁和current块。
这五步里的关键点在于:真正传输的是已经构造好的CR镜像,A并没有等待B把脏块写盘再读,因此整个路径的耗时几乎完全取决于私网延迟和GCS消息处理速度。这也解释了为什么RAC对私网要求那么苛刻——Cache Fusion的性能上限,本质是私网延迟的上限。
2.4 怎么观察PCM流量
日常巡检最常看的几个GCS统计项包括:
- gc cr blocks received / served
- gc current blocks received / served
- gc blocks lost
- gc remaster
在RAC每个节点上查gv$sysstat,可以快速确认哪个实例在大量“产出”或“消费”数据块。配合等待事件里的gc cr request、gc current request平均等待时间,基本能判断块传输是否成了瓶颈。
检查某个段在哪些实例上有缓存分布,可以用类似下面的SQL:
sql复制select inst_id, state, count(*)
from gv$bh
where objd = :data_object_id
group by inst_id, state
order by inst_id, state;
如果某个热块的绝大部分副本都集中在实例B上,而业务访问主要从实例A发出,那么大概率会看到大量gc cr request。解决方向不是调整参数,而是审视业务连接分配、应用分区策略,或者考虑让访问更偏向块所在实例。RAC不喜欢“打一枪换一个地方”的随机访问模式,而是喜欢相对聚集的访问模式。
3. 非PCM资源:集群的“红绿灯系统”
PCM资源负责的是数据块,但RAC里另一大类资源经常被忽略,直到出现library cache lock、row cache lock这类等待才有人想起。这类资源就是非PCM资源。
3.1 非PCM资源到底管什么
非PCM资源的范围非常宽,可以粗略分成三组:
第一组是全局enqueue队列锁,比如TM锁(表级DML锁)、TX锁(事务锁,在跨实例行锁冲突场景下会暴露)、UL锁等。这些锁的特点是语义复杂、需要在请求方之间公平排队,典型的等待事件是enq: TM - contention、enq: TX - row lock contention。
第二组是库缓存锁(Library Cache Lock和Library Cache Pin)。SQL、PL/SQL对象、表定义在多个实例间都要保证版本一致性和并发安全。一个实例做DDL变更,另一个实例正在执行相关SQL,就可能在库缓存对象上发生跨实例冲突。等待事件library cache lock/pin在RAC里往往比单机更明显的原因就在这里,它需要所有节点协调。
第三组是字典缓存锁(Row Cache Lock)。数据字典缓存中保存的文件信息、段信息、用户权限等,在实例间同样需要一致。如果某个操作频繁触发字典缓存失效或刷新,就可能出现row cache lock,这类等待在RAC环境里有时会被误判为普通latch竞争。
从管理服务上看,这些非PCM资源由GES统一协调。GES和GCS共享底层的消息通信设施,但逻辑职责完全不同:GCS聚焦块的快速传输,GES聚焦排队和公平。
3.2 GES为什么要设计成排队型机制
数据块PCM资源追求的是最短路径,所以Oracle允许状态快速转换,甚至直接由master转发块。但对非PCM资源来说,请求方之间往往存在“互斥”和“优先级”语义,简单地用S/X锁不一定够,还需要按请求顺序排队。
举个很直观的例子:两个实例同时对同一张表执行DDL以外的DML,一个做insert,一个做truncate,数据库必须保证truncate和DML不会同时进行。这个时候需要TM锁在全局范围内协调。TM锁的持有模式和请求模式很多,有NULL、SS、SX、S、SSX、X六种组合,如果只靠PCM那套简单S/X二态模型,很多并发场景根本表达不清楚。
所以GES为每个enqueue类型维护了队列结构,记录谁请求了什么模式、谁在阻塞谁。用v$lock视图就可以看到当前会话持有的各种锁:
sql复制select inst_id, sid, type, id1, id2, lmode, request, block
from gv$lock
where type in ('TM', 'TX')
order by type, id1, id2;
当某个会话被阻塞时,v$session里的blocking_session能帮你快速找到阻塞源头,然后再去对应的节点上定位是什么操作持有锁不释放。
3.3 跨实例间的非PCM资源竞争典型场景
在RAC里,非PCM资源竞争有一个非常经典的放大场景:大量进程同时执行未共享的SQL,导致每个节点都需要向GES申请相同的library cache lock,结果整个集群的硬解析被串行化。单机上最多是library cache latch的本地竞争,RAC里则会表现为跨节点的library cache lock,影响面瞬间扩大几倍。
row cache lock也类似。每当Oracle需要访问新的数据字典对象或文件信息时,会先在本地row cache查询。如果本地没有缓存或缓存失效,就需要从系统表空间加载,加载过程中要在多个实例间获得字典缓存锁。RAC中如果一个断电导致实例启动,所有节点同时尝试加载大量字典缓存,极容易出现大范围的row cache lock等待,甚至拖慢整个集群的启动后的访问。
这类问题的解决套路通常是“降低对非PCM资源的无效请求频率”:SQL绑定变量要到位,避免成百上千种不同文本的SQL;DDL要避开业务高峰;data dictionary cache相关参数不要随便调小;对于确有必要频繁变更的段,考虑分级分区管理,别在高峰做大动作。
4. PCM与非PCM资源的协同与故障场景
把两类资源分开理解只是第一步,真正的难题在于它们不是孤立的。一次简单的事务操作可能在几毫秒内交替触发PCM资源和非PCM资源,任何一个环节阻塞,表现在应用侧都可能只是“数据库卡了一下”。
4.1 一次普通更新事务背后的资源流转
假设应用连接到实例A,执行一条UPDATE语句。这条语句先说需要修改某张表的某一行:
- 首先要在表对象上获得TM锁,这是一个非PCM资源请求,可能涉及GES和master节点协调。
- 如果该行的行锁已被其他会话持有,还要进入TX锁的等待队列,这部分也是enqueue机制。
- 然后数据库定位到对应数据块,如果块不在本地buffer cache,或者本地只有CR副本而需要current版本去修改,就触发PCM资源请求。
- 如果块的最新版本在实例B手上,实例A需要获得X模式PCM锁,B把current块传过来,并把自己的旧拷贝标记为past image。
- A拿到X锁后才能修改buffer cache里的块,生成redo和undo。
- 事务提交或回滚时更新相应资源状态,释放TX锁和TM锁。
如果只看PCM,会以为数据块传过来就万事大吉;其实前面的TM锁和TX锁协调如果慢了,block传得再快也白搭。AWR里经常看到gc cr request与enq: TM - contention同时出现,本质就是两类资源都在参与同一批操作。
4.2 DRM动态资源重主(Dynamic Resource Remastering)
RAC不是静态集群。默认情况下,PCM资源的master是分布式散落的,当业务热点集中在某个实例访问某些段时,Oracle会自动跟踪,把一个或多个段的资源master迁移到热点实例上,让资源协调尽可能本地化。这就是DRM。
DRM的好处很直接:减少跨实例消息,降低gc等待。但DRM本身也是一把双刃剑,触发重主时会短暂地冻结相关资源的访问,表现为gc remaster等待。如果AWR里gc remaster占比很高,往往说明系统里存在很多小段并且访问模式剧烈变化,或者频繁出现段级别热点漂移,导致Oracle一直在做资源重主迁移。
遇到这种情况,可以优先优化应用访问模式,让稳定的大表访问留在固定节点;也可以考虑禁用或调大DRM的触发阈值。网上很多“RAC调优建议关DRM”的说法,不全对,但对业务模式非常跳跃的系统,关闭或限制DRM确实能减少无谓的reconfiguration开销,只是在做之前需要仔细评估哪些段的热点很稳定,值不值得保留自动重主的能力。
4.3 节点故障后的资源重构建
当集群中某个实例宕机或被驱逐出集群时,它曾经负责的PCM资源和非PCM资源master身份都会发生大规模迁移。幸存的实例需要重新接管这些资源、重建GRD状态,同时处理失败实例遗留的buffer cache、锁和未完成事务。这个过程俗称reconfiguration。
reconfiguration期间,集群可能会经历明显的性能抖动,大量会话等待“reconfiguration”相关事件。如果集群规模大、buffer cache里缓存了很多数据块,或者失败的实例恰好master了大量热门段资源,恢复时间就会被拉长。
所以判断一个RAC是否健壮,不能只看正常运行时的缓存融合表现,还要定期演练节点故障。否则真正宕机那天,你会发现PCM资源重构建的耗时远超预期,整个业务窗口被无情拉长。节点故障处理涉及的CSS投票、脑裂驱逐等机制,也是RAC运维里的核心课题。
5. 实操经验:用PCM与非PCM的思路排查问题
有了前面这些底子,再看常见的RAC问题会清晰很多。很多所谓疑难杂症,本质上只是没有把问题分类到对应的资源域而已。
5.1 一步到位的排查路径
当RAC出现性能问题时,我建议先按这个顺序走一遍:
首先把AWR里Top 10等待事件列出来,区分哪些是gc前缀的PCM类等待,哪些是enq/library cache/row cache前缀的非PCM类等待。如果两类等待同时严重,优先解决非PCM的那部分,因为它们往往会制造大量跨实例阻塞,进一步触发更多PCM块访问。
然后针对PCM类等待,用gv$sysstat看哪些节点在大量接收块、哪些节点在大量服务块,再结合gv$bh热段分布判断访问确实走偏了。针对非PCM类等待,用gv$lock定位锁的持有者与阻塞关系,从阻塞头开始一层层找,大多数时候根因都是某一两条SQL或某个DDL。
最后用实际会话的实时等待去验证。直接查v$session的event、blocking_session、p1/p2参数,能拿到远比AWR更精准的现场。AWR是事后视角,实时视角才能真正抓住“此时此刻资源被谁占用”。
5.2 表空间满问题为什么在RAC里容易被放大
表空间满对单机数据库来说就是个扩容或清理问题,在RAC里却往往没那么简单。表面上看到的是ORA-01653无法扩展表空间,但实际影响可能牵涉到多个实例。
当数据文件自动扩展或手工resize时,Oracle需要更新控制文件和字典信息里的文件大小条目,这个过程会触发布局在数据字典缓存中的元数据变化,在RAC里就容易出现row cache lock跨实例竞争。如果业务正在高峰,几个实例同时访问字典缓存,完全可能因为一次自动扩展引发连锁等待。
更隐蔽的是临时表空间。RAC里每个实例的临时表空间都要能访问同一个临时表空间组,如果建库时临时表空间配置不对,某些实例会一直使用单独的临时文件而不参与组管理,最终在排序量大时频繁报临时表空间不足。排查时不能只看一个节点,要用v$tablespace和v$tempfile确认所有实例看到的临时文件一致,避免“一个实例够用,另一个实例爆掉”的诡异现象。
扩容建议也和使用场景绑定:如果是普通表空间,优先考虑在ASM磁盘组有足够空间的前提下向原有表空间增加数据文件;如果是临时表空间,建议使用临时表空间组,让多个实例的排序操作分散到不同临时文件,减小单文件压力。日常巡检时多关注dba_data_files的autoextensible状态和maxsize,防止“明明开了自动扩展,却因为达到maxsize直接报满”。
5.3 19c RAC里数据文件创建到了本地盘:一个经典的架构误操作
搜索热词里有“Oracle 19c RAC 数据文件创建到了本地盘”,这几乎是每个初学RAC的人都会踩的坑,也特别适合用来验证你是否真的理解了缓存融合的边界。
有人会想:RAC不是有Cache Fusion吗?节点A创建了一个表空间,数据文件在A的本地磁盘上,节点B如果访问这个表空间,A完全可以把数据块通过私网传给B啊。听起来好像可行,实际完全不行。
原因是Cache Fusion融合的是buffer cache里的数据块,不是文件系统的I/O能力。当数据块在A的本地磁盘上被读入A的buffer cache后,它确实可以被传到B,但以下场景会立刻崩盘:
- B需要执行DBWR写脏块:B产生修改后,要把块写回数据文件,它根本找不到本地对应的磁盘路径。
- B实例执行介质恢复或实例恢复:需要打开所有数据文件,发现文件不存在或不可访问,直接报ORA-01157。
- 节点A宕机后,B尝试接管数据库,整个文件对B不可见,集群无法继续运行。
所以在RAC里,所有数据文件必须存放在所有实例都能访问的共享存储上。生产环境优先选择ASM或共享文件系统,绝不能放在某一个节点的本地磁盘。这个案例也再次说明:PCM只负责把块从内存传到内存,底层文件访问仍然要求每台机器都有物理路径。理解了这个边界,就不会把RAC误当成“可以把存储变成分布式的万能方案”。
5.4 别把GDS和RAC搞混
另一个搜索热词是“Oracle GDS和RAC的区别”。这俩经常被放一起讨论,是因为都带“全局”和“集群”的概念,但解决的问题完全不同。
RAC是把同一个数据库的多个实例组合起来,共享一套数据文件,依靠Cache Fusion做缓存一致性,扩展的是单数据库的算力和可用性。
GDS(Global Data Services)则是管理多个独立数据库服务的框架。GDS下面的每个数据库可以是RAC集群,也可以是单实例,甚至可以是Data Guard备库。GDS的核心是把这些数据库的服务统一注册、统一路由,实现按负载分发、故障切换、复制集群之间的读负载均衡。可以理解成站在RAC更上层的调度器。
一个常见架构是:两套RAC(比如北京一套、上海一套),每套内部各自用Cache Fusion保证数据一致,两套之间再用Data Guard同步,最后用GDS把应用连接按地域或角色分发到两套RAC上。RAC解决的是“同一个库怎么横着扩”,GDS解决的是“多个库该怎么对外提供服务”。
如果混淆了这个边界,排查问题时就会出现方向性错误:明明是在GDS层面配置的服务路由问题,却跑到RAC的PCM等待事件里找原因,自然会卡住很久。
5.5 旧版本RAC安装时的兼容性提醒
搜索热词里还有“redhat 7.9 安装 oracle 11gr2 rac”。这里得单独提醒一句:11gR2推出年代较早,对较新的Linux发行版支持并不好。很多人在RHEL 7.9上装11gR2 RAC,会卡在集群件无法启动、ohasd起不来、root.sh执行报错这些地方。
根因不一定是步骤错,而是Oracle 11gR2 Clusterware使用的初始化脚本体系还停留在SysV init时代,RHEL 7之后已经改用systemd,两者兼容性需要额外适配。即便费劲装完了,OLTP场景下也可能因为旧版本对新内核的锁和调度机制处理不佳,出现莫名其妙的性能问题。
如果生产环境规划是从零开始,我会强烈建议优先选择官方认证匹配的OS和数据库版本组合,比如Oracle Linux 7 + 11.2.0.4的官方认证组合,或者干脆升级到19c及以上版本。内存融合、GCS/GES这些核心机制在19c上依然继承自11g的设计,但整体稳定性和兼容性要好得多。技术可以追新,生产环境千万别用“硬凑”的方式去挑战兼容矩阵,集群资源协调层一旦不稳定,后面所有PCM和非PCM资源的配合都会跟着出问题。
6. 给运维和开发各留一句实在话
从2000字聊到这儿,最后说点个人体会。
对DBA来说,RAC的问题排查永远要记住“先分类,再深挖”。PCM问题多半和数据分布、访问热点、私网质量有关;非PCM问题多半和锁排队、字典缓存、SQL共享性有关。两类问题混在一起时会互相放大,但只要先用等待事件把它们拆开,下一步的排查方向就不会跑偏。
对开发人员来说,理解PCM和非PCM资源存在的意义后,你写出的SQL就应该有“跨实例意识”:不要放任随机性的小事务跨节点反复读取同一份热数据,不要频繁执行未共享的DDL,更不要让应用在多个实例间随机漂移。RAC的数据库结构是共享的,但高性能访问路径永远是“热点需要相对集中”的。能在一个节点把事办完,就别折腾整个集群。
内存融合技术从9i到23ai,本质上没有颠覆性变化,变的只是细节和智能化程度。把那套资源协调的语言刻进脑子里,以后无论是看AWR还是处理gc/阻塞类问题,都会轻松很多。
