做RAC性能诊断这几年,我最大的感受是:很多人不是在跟数据库故障较劲,而是在跟一堆术语较劲。明明几百个会话都在等一个事件,你翻着AWR报告看到gc cr request、gc buffer busy acquire,又看到enq: TX - row lock contention,心里隐约知道都跟“锁”有关,但要让你说清楚哪些是内存融合在传数据块,哪些是全局队列在串行化,立刻就开始含糊。更不用说PCM、非PCM、GCS、GES这些缩写放在一起,简直就是一场声部交错的交响乐。这篇东西我打算换个讲法:不摆官方架构图,而是从一条update语句跨节点执行的全过程入手,把内存融合技术里的PCM资源和非PCM资源各自的角色、管理机制、典型等待事件,以及我在实际环境里排查这些问题的经验,一次讲明白。如果你正在维护11.2、12c、19c的RAC,或者准备从单实例转RAC,这篇文章能帮你少走很多弯路。
需要先提醒一句:RAC里的PCM,全称是Parallel Cache Management,指GCS针对数据块资源的全局缓存管理。别一搜PCM搜出来一堆音频编码、PCM线缆的文章,那是另一个世界的东西。我们这里聊的是Oracle集群里最核心的缓存一致性协议。
1. 先看一次跨节点更新:PCM和非PCM是怎么分工的
1.1 单机缓存命中变成了全局协调
在单实例数据库里,一条update语句要修改某一行的数据时,数据库把该行所在的数据块先读入本地buffer cache,然后在内存里做修改。修改前要把数据块状态改成“当前版本”,并通过undo生成旧版本,便于回滚和一致性读。整个过程不需要告诉别人“我来了”,因为整个数据库只有我一个实例。
到了RAC,事情就不一样了。同样一条update语句可能落在节点1,也可能落在节点2。如果节点2要修改某个数据块,而这个块的“最新版本”此刻正被节点1的内存缓存着,节点2最笨的办法是等节点1把块写回磁盘,然后再去磁盘读。但Oracle没有这么干。RAC的私网上直接把这个块的内存版本从节点1传给节点2,写盘变成了跨节点传内存,这就是内存融合(Cache Fusion)。也是RAC区别于普通共享存储集群的关键:块不需要写进磁盘,就能在实例之间移动使用权。
这一瞬间的传块动作,靠的是PCM资源管理。数据块并不是单纯被“传”过去,而是伴随着一个全局权限转移。谁有权限读、谁有权限写、谁保留了旧版本,这些都要被记录清楚,否则一个块在不同节点的内存里就出现了不一致。
1.2 两类资源的分界:GCS管块,GES管锁
PCM资源在Oracle内部由GCS(Global Cache Service)负责。GCS管理的是buffer cache里实实在在的数据块。包括表块、索引块、undo块,它们的全局读写权限全部走GCS。
然而数据量里除了数据块,还有一类东西没法传块,例如一个表的结构定义、一条SQL的解析游标、数据字典信息、SCN、系统变更号、事务表条目等。这些东西如果跨节点不一致,同样会出大问题。它们由GES(Global Enqueue Service)管理,日常我们叫它非PCM资源。非PCM是相对于PCM的一个说法,意思是“不是通过数据块融合方式来协调的全局资源”。
GCS和GES一起构成了RAC底层的分布式锁管理机制。有些资料把两者统称为DLM或者Cache Fusion体系,但严格讲:涉及数据块内容的并发协调是GCS的活,涉及各种队列锁和对象级一致性的是GES的活。后面你会看到,一条简单SQL会同时踩到这两个服务。
1.3 一条update语句的完整旅程
我举个例子。假设一个订单表orders,节点1上的会话A执行了:
sql复制update orders set status = 'PAID' where order_id = 100;
此时节点1需要做几件事。
第一,解析SQL,检查用户权限。解析过程中会访问数据字典、库缓存。如果orders的表定义在节点2的内存字典缓存里还没有同步,节点1就需要通过GES对相关字典资源做全局请求,保证看到一致的定义。这里出现的等待是类似row cache lock这样的非PCM等待。
第二,在buffer cache里找到order_id=100这一行的数据块。如果节点1没有该块,它就要先向该数据块的资源主节点(resource master)发起PCM请求。主节点查看全局资源目录,发现这个块正被节点2以X模式持有。于是节点2把块的最新版本通过私网直接传给节点1。这个过程会出现gc cr request或gc current block busy等以gc开头的事件。这就是PCM资源的协调。
第三,拿到了数据块之后,会话A需要在该行上加行级TX锁。这个锁需要被两个节点共同看到,否则节点2另一个事务也来改同一行时,无法知道已经被节点1锁住。TX锁的全局协调由GES完成。于是节点2上更新同一行的会话B会阻塞在enq: TX - row lock contention。你看到这个事件时,就说明GES在工作,它管理的非PCM资源产生了冲突。
你看,一次简单的update,PCM资源负责“谁能拿到数据块并修改块内容”,非PCM资源负责“谁能拿到全局队列锁来保证并发事务之间相互可见”。两者不在同一个层面,但缺一不可。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. PCM资源深度:数据块的全局权利交接
2.1 GRD、资源主节点和持有者列表
想看懂PCM,先要接受一个概念:RAC里的每个数据块,都在一个名为GRD(Global Resource Directory)的目录里有登记。GRD并不存数据,只记录资源名、资源主节点、当前持有者、请求者队列状态等信息。
这里的“资源名”不是文件名,而是根据数据块地址、对象ID、文件号、块号生成的内部标识。资源主节点由资源名参数用哈希算法分散到不同实例上。例如编号为123的资源主节点可能在节点1,编号为456的资源主节点在节点2。这样设计是为了让资源管理请求的负载在集群中均匀分布,而不是所有节点都跑到一个内存目录去询问。
当节点A需要读某个块时,它必须先知道这个块的资源主节点在哪。最简单的办法是本地算哈希,或者通过LMON进程维护的映射。拿到主节点地址后,节点A给主节点发送请求,主节点查询GRD,判断块的权限目前被谁持有,然后调度持有者把数据传输给请求节点,最后更新GRD中的持有者列表。
这个模型解释了一个常见现象:为什么私网延迟哪怕只有零点几毫秒,在高并发时也会被无限放大。因为一次块访问往往不是节点间直接对话,而是请求方、主节点、持有方三者之间发生多次消息交互。每次交互都要经过网络,网络质量就是PCM的命脉。
2.2 PCM资源的模式和过去影像
GCS给数据块资源定义了类似传统锁的模式,但也允许在不同节点间组合持有。简化理解:
- N模式:无访问权限,本地没有该块的合法副本。
- S模式:共享读权限。多个节点可以同时以S模式持有同一个数据块。
- X模式:独占权限,同一时刻只有一个节点能持有。
节点A要修改块,就需要把资源模式变成X。如果资源当前被节点B以S模式持有,那么节点B上所有非必要的CR副本需要被失效,然后节点B把当前块传给节点A。如果节点B当前以X模式持有,那就更直接,节点B把块交出来。这里的“交出”不是指节点B的内存里必须扔掉这个块,而是块的所有权转移给节点A。
Oracle在这里做了一个非常聪明的优化:节点B在交出X块时,本地可以保留一份“过去影像”(Past Image,简称PI)。过去影像不是一个普通的CR副本,它是为了满足后续其他节点的一致性读而准备的。当节点A拿到块并修改后,如果有节点C请求读这个块的CR版本,节点A虽然持有当前块,但它还要去构造旧版本,可能需要借助节点B保留的PI来减少undo读取的开销。所以一个X块在跨节点写来写去时,多个节点可能同时保留着这个块的不同版本影像。这些影像在GRD里都要有登记。
理解PI很重要。因为gc cr grant 2-way、gc cr grant 3-way这些统计,描述的就是CR块从哪个节点、经过几方协同传过来的。如果PCM传输频繁,你会看到gc cr blocks received不断增长。这个值本身不是故障指标,但如果它伴随着单次接收时间变长,那就需要排查网络和负载了。
2.3 从等待事件看PCM资源到底卡在哪
排查PCM问题,先看等待事件时带“gc”关键字的那一批。下面几个是我在实际环境中经常见到的典型:
| 等待事件 | 含义 | 常见排查方向 |
|---|---|---|
| gc cr request | 本地请求一个远端构造的CR块,正在等待 | 私网延迟、远端CPU、CR块构造慢 |
| gc current block busy | 请求某个当前块,但当前块正忙或持有者无法立即提供 | 热块争用、节点间传块延迟 |
| gc buffer busy acquire | 多个进程在等待buffer上完成全局操作 | 热块、DRM迁移、IO慢 |
| gc cr block lost | 传过来的CR块在私网中丢失导致重传 | 私网丢包、网卡驱动、防火墙 |
| gc remaster | 资源主节点正在重新分布 | DRM发生,可能引起短暂卡顿 |
查这些等待的重点不是只看事件名,还要看P1、P2、P3参数。比如gc cr request的P1通常是文件号,P2是块号,P3是资源主节点编号。你可以从v$event_param_name或者v$session_wait里找到对应值,然后和某个具体的表、索引关联起来,定位到底是哪个对象在被跨节点抢。
另外还有一类事件是“属于当前块的持有者端”的反应,比如gc current block busy经常出现在持有者节点正忙着把块写给别的节点。如果你看到某节点大量出现这类事件,可以看看那个节点的LMS进程(Global Cache Service Process)CPU占用率是不是特别高。LMS就是GCS在实例上的工作进程,它负责通过网络传送数据块,一个LMS忙不过来时,块传输就会排队。
2.4 DRM动态资源主节点再平衡
不知道你有没有在AWR里见过gc remaster这个事件,时间还不短。这跟DRM(Dynamic Resource Mastering)有关。默认情况下,资源主节点是按照哈希分散的,但访问模式并不均匀。例如一个热点表的数据块总是被节点2访问,但资源主节点却在节点1,那每次都要远程请求节点1做协调。Oracle为了优化这个问题,在后台会根据访问频率动态把一部分资源主节点迁移到频繁访问的实例上,这个动作就是DRM。
DRM听起来很美,但代价是迁移过程中相关资源需要短暂的“冻结”来保证GRD一致,此时正在访问这些块的会话就可能等gc remaster。在11g、12c早期版本,DRM策略有时候过于激进,导致生产集群出现周期性的性能抖动。到了19c,默认的DRM策略调整以后,这类抖动少了一些,但并没有消失。
所以当你看到gc remaster大量出现时,可以先去查询gv$sysstat里关于DRM的统计,也可以关掉部分自动DRM的阈值,但这属于比较手术刀式的调整,没掌握足够监控数据之前不要乱动。我更建议先观察一段时间,看gc remaster是不是和应用的批量任务周期重合。如果每次大批量导入时都出现,说明大批量任务改变了数据块的访问模式,Oracle在花代价重新布局资源主节点,这时候需要考虑对大任务做分区裁剪或并行策略优化,而不是无脑禁掉DRM。
3. 非PCM资源深度:那些没有数据块可传的全局锁
3.1 非PCM到底包括哪些东西
如果说PCM资源的目标是“数据块在缓存中的一致性”,那非PCM资源要管的就是范围更杂的全局对象。常见的有这么几类:
- 库缓存资源:对象句柄、SQL游标、PL/SQL程序单元等。
- 数据字典缓存资源:字典对象、序列、用户信息等。
- 各类enqueue:比如TX(事务锁)、TM(表锁)、HW(高水位锁)、CF(控制文件锁)、SQ(序列锁)等。
- 一些内部协调资源,比如实例恢复、文件状态切换等所需的全局锁。
这些资源不能被切成一块块数据在节点间传输,它们天生就是“锁”的语义。所以GES会为它们维护全局请求队列和持有者列表。你可以把GES理解为集群里的队列仲裁员:多个节点想要同一个非PCM资源时,由它按先来后到和锁模式兼容性来放行。
有一个容易混淆的点:用户会话直接在v$lock视图里看到的锁,例如TX、TM,和RAC引入的GES锁并不是两套完全独立的东西。在RAC里,本地产生的enqueue信息会被发送到GES进行全局登记和协调。比如一个事务在节点1取得TX锁,其他节点要判断同一行是否被锁时,其实是通过GES查询全局TX锁的状态来得知的。所以在RAC中,所谓“本地锁”的很多后台协调也离不开GES。
3.2 TX锁竞争在RAC里的真实表现
两个节点同时更新同一行的场景我在前面已经提过。节点1先修改不提交,节点2再修改,节点2会出现类似下面的等待:
code复制enq: TX - row lock contention
这不是数据块的传输瓶颈,而是应用层并发事务没有规划好。GES只是在忠实地告诉你:那一行的行级锁已经被节点1的一个未提交事务持有,而你正排在后面。
这种问题常见的处理思路,不是调内存融合参数,而是查应用逻辑。看看为什么会有多个节点同时修改同一行。例如批量更新语句没有按主键分批,而是两个实例同时扫描同一张表并更新相同范围内的订单,就会撞车。或者前端应用通过负载均衡把长事务随机发到了不同节点,加剧了跨节点锁询问。先用这段SQL找出阻塞会话:
sql复制select g.inst_id,
g.sid,
g.serial#,
g.event,
g.blocking_instance,
g.blocking_session
from gv$session g
where g.blocking_session is not null
order by g.blocking_instance, g.blocking_session;
拿到阻塞者后,确认它在跑什么SQL、事务持续多长时间。如果只是偶尔几个会话撞到一起,正常的短事务锁等待不需要过度干预。但如果你看到平均等待时间动不动几十秒,说明有事务长期不提交,或者应用在事务里做了外部接口调用,把行锁挂了一整天。这种问题跟RAC没关系,单实例同样会发生,只是单实例里你更容易通过v$lock看到它。
3.3 library cache lock、row cache lock的RAC形态
非PCM资源里,比TX锁更难排查的往往是library cache lock和row cache lock。
library cache lock:例如节点1执行了一个DDL语句,比如alter table orders modify status varchar2(10),它需要对orders对象句柄做兼容性检查,阻止别的事务在DDL执行期间去访问或修改同一对象。此时节点2正在大量执行orders表上的SQL,这些会话会被阻塞,表现为library cache lock。在单实例里DDL也会引起library cache lock,但RAC会把这个等待放大:因为不同节点上的会话都要协调同一个对象句柄,GES要做更多消息传递。
出现这类等待时,第一反应应该是看AWR里Top 5事件和Top SQL,有没有刚被编译过的对象。第二查DDL是否使用ONLINE选项。比如重建索引没有使用ONLINE,在一个大表上跑了一个小时,期间所有跟该表相关的DML都会在对象级被阻塞。实际发生过多次这样的事故:白天有人偷偷跑rebuild index,RAC两个节点上的应用会话瞬间堆满,表现就是全库性的library cache等待。
row cache lock通常和字典缓存有关。经典的场景是频繁创建临时表、频繁修改序列、或者使用了大量不同对象ID的重复解析。当你看到row cache lock时,先用v$rowcache查一下什么字典缓存项竞争最激烈。如果某个序列通过cache 20默认参数冲高并发,所有刷缓存动作都发生在一个节点上时,另一个节点即使拿到了新值,也需要在全局范围更新字典缓存,这时就可能出现row cache lock。常规解法是把序列的CACHE值调大,或者改成全局序列;如果实在无法避免,可以考虑把序列请求绑定到单一节点,减少跨节点字典缓存刷新。
3.4 非PCM资源的视图与等待事件解码
在动态性能视图里,非PCM资源并不像数据块那样有非常直观的对象名。不过我们可以通过等待事件迅速归类:
- 事件名以
enq:开头,后面跟两位锁类型字符,例如enq: TX - row lock contention、enq: TM - contention、enq: HW - contention。这些都属于GES管理的enqueue。 - 事件名和
library cache、row cache相关的,也属于GES管理的缓存资源。 - 事件名带
ges或gcs的,属于DLM内部协调事件,例如ges remote message。
如果你拿到一个会话的P1参数,想解析锁类型,可以看P1P。举个例子,对于enq: TX - row lock contention,P1的前两个字符就是16进制的TX。下面的SQL可以帮你照抄出来:
sql复制select sid,
event,
p1,
chr(to_number(substr(to_char(p1, 'XXXXXXXX'), 1, 2), 'XX')) ||
chr(to_number(substr(to_char(p1, 'XXXXXXXX'), 3, 2), 'XX')) lock_type
from v$session
where event like 'enq:%'
and p1 is not null;
但日常排查,其实不需要每次都用这种方式。绝大多数情况下,从事件名称就能判断它大概是哪种类型的锁。真正的难点在于判断“这个锁是被谁堵住的”,那需要结合blocking_session和blocking_instance,再配合v$sql查询正在执行的SQL。工具上没有太多花活,关键是思路要清晰:先定位等待事件属于PCM还是非PCM,再看持有者,再看持有者为什么没释放。
4. 实操观测:用SQL实时监听PCM和非PCM资源
4.1 一张会话等待视图查出当前争执双方
排查RAC性能问题时,我第一个习惯性动作是看实时等待,而不是马上翻AWR。AWR是T-1小时的平均值,可能把瞬时抖动淹没;实时视图能看到此刻正在发生的矛盾。用这段SQL查所有非空闲会话:
sql复制select inst_id,
sid,
sql_id,
event,
p1,
p2,
p3,
wait_class,
seconds_in_wait,
blocking_instance,
blocking_session
from gv$session
where wait_class <> 'Idle'
order by inst_id, seconds_in_wait desc;
输出结果通常不会让人失望。如果看到一大片gc cr request,那说明问题集中在PCM资源的数据块传输上面。如果看到整片enq: TX - row lock contention,那问题主要集中在GES管理的非PCM资源。如果看到library cache lock,你需要马上关注最近有没有DDL或存储过程编译动作。
只看当前会话还不够,因为某些等待可能已经过去。这时可以翻ASH。下面这条SQL可以按事件统计最近一小时的采样数:
sql复制select sample_time,
event,
count(*)
from gv$active_session_history
where sample_time > sysdate - interval '60' minute
and event is not null
and session_state = 'WAITING'
group by sample_time, event
order by sample_time;
ASH的价值在于能帮你看时间线。比如你发现每到一个整点,gc cr request就出现峰值,那多半是定时任务或Java定时器在同一时间触发跨节点的批量扫描。这时去和业务方核对定时任务调度,比在数据库参数里折腾半天有效得多。
4.2 把PCM和非PCM事件做成你的速查表
如果你刚接触RAC,面对一大堆等待事件可能会发懵。我建议你直接在本地记一张速查表,遇到问题先按事件名前缀归类。给个我自用的精简版:
| 前缀/关键字 | 资源归属 | 可能原因 | 首要动作 |
|---|---|---|---|
| gc cr / gc current / gc buffer / gc remaster | PCM(GCS) | 数据块跨节点传输、私网慢、热块、DRM | 查私网丢包/延迟,定位对象,考虑服务绑定 |
| enq: TX - row lock contention | 非PCM(GES) | 行锁冲突 | 查blocking session,定位SQL/事务 |
| enq: TM - contention | 非PCM(GES) | 表锁/DDL冲突 | 查DDL或外键是否缺少索引 |
| enq: HW - contention | 非PCM(GES) | 高水位扩展争用 | 检查段空间管理,可能需要收缩或批量插入优化 |
| library cache lock | 非PCM(GES) | 对象句柄争用,DDL/reload | 查DDL和硬解析,定位对象 |
| row cache lock | 非PCM(GES) | 字典缓存争用 | 查v$rowcache,优化序列/字典访问 |
| library cache pin | 非PCM(GES) | 对象正在被修改/编译 | 定位pin持有者 |
这张表不要死记,核心就一条:事件名里带gc的,先按数据块传输去排查;带enq:、cache lock、cache pin的,先按全局锁排队去排查。
4.3 实例:数据文件放到本地盘的“伪内存融合陷阱”
遇到过不止一个朋友在19c RAC上踩过这个坑:感觉RAC既然有内存融合,是不是数据文件不一定非要放共享存储?于是有人在节点1的本地文件系统上建了一个表空间,结果节点2的会话一访问就报错。
这里必须强调,内存融合(PCM)解决的是“数据块已经存在于某个节点buffer cache时,如何把它传给其他节点”的问题。如果数据块目前不在任何节点的内存里,比如数据库刚刚启动、数据块被LRU淘汰、或者实例发生了重启,那么节点2要读取这个块,就必须通过本地的IO路径去访问磁盘。如果文件只存在于节点1本地磁盘,节点2根本看不到这个文件,自然就无法读取。无论GCS怎么努力,它都不可能把一个不存在的磁盘路径变成共享存储。
这个案例给我们的教训是:PCM不改变RAC必须共享存储的基础要求。数据文件、控制文件、重做日志必须放在所有实例都能访问到的存储系统上,比如ASM、集群文件系统,或者SAN/网络存储。如果业务有特殊需求要在某个节点临时创建本地文件做测试,也要记得及时删除,千万不要让数据库的永久对象出现在本地盘上,否则一旦实例故障切换到另一个节点,整个实例可能因为无法打开那个文件而崩溃。
某种意义上,这不仅仅是PCM资源的问题,但它很典型地说明了一个误区:很多人把RAC理解成“多节点共享一切数据”,实际上RAC的共享能力是建立在底层的块设备和分布式锁机制之上的,一层不到位,上层的融合也就无从谈起。
5. 内存融合的软件和硬件底牌:私网、服务、参数
5.1 私网传输就是Cache Fusion的命脉
既然PCM资源的每一次块传输都走私网,那私网的延迟和带宽直接影响融合的效率。不要以为RAC对私网要求不高,如果你的应用里有大量跨节点读热块,那么私网吞吐量和网卡中断负载会非常惊人。我见过一些两节点的11g RAC跑OLTP,每分钟私网传输的数据量也能到好几个GB。这不是少数情况。
检查私网状态时,先看视图:
sql复制select * from gv$cluster_interconnects;
这个视图会显示当前集群互连使用的IP和网卡。确认它用的是你规划好的千兆/万兆私有网段,而不是业务网段。如果业务流量和集群心跳混在一起,高峰期的业务流量会挤占PCM传输带宽,导致RAC整个集群的gc等待飙升。
在Linux环境下,可以简单用ping测私网延迟和丢包。如果计划开启巨型帧/Jumbo Frame,需要确认交换机、网卡驱动和应用都匹配。测试时可以把包大小设为8972字节:
bash复制ping -c 100 -M do -s 8972 <对端私网IP>
如果出现丢包或延迟很高,优先检查网卡绑定方式、交换机端口协商速率。另外要注意,有些网卡默认开启了节能模式,在持续小包传输时延迟会变得不稳定,这类的现象就是AWR中gc cr request平均时间不算特别长,但经常有尖刺。建议关闭网卡的节能模式,并关闭接收端合并的极端参数,这些都是基础网络优化。
5.2 把热块留在本地:服务分配和DRM的影响
PCM资源最怕的基础场景是“热块在多个节点之间反复横跳”。两个实例同时高频读写同一个索引根块、同一个段头块、同一行数据时,GCS需要不断做模式转换和块传输。即使私网再好,每次都传输也会产生额外开销。所以RAC架构下,应用层面的“亲和性”有时候比数据库参数更能调节性能。
从应用层面,可以通过Oracle Service把不同模块的连接绑定到不同实例。比如oltp_app服务只运行在节点1,report_app服务只运行在节点2,尽量减少同一份数据被两个节点同时高频访问。当然,如果两张表有外键关系,而它们被绑定到了不同节点,反而可能导致大量跨节点字典/锁请求,所以绑定服务时还要考虑数据依赖关系。
从数据库层面,DRM会尝试把资源主节点迁到高频访问的节点。在资源主节点迁移完成后,该节点上对这些资源的访问会变快。但迁移过程也可能伴随短暂的gc remaster。观察DRM是否频繁,可以用如下命令检查统计:
sql复制select inst_id, name, value
from gv$sysstat
where name like 'gc remaster%'
order by inst_id;
如果gc remaster频率很高,同时应用间歇性出现秒级卡顿,那你可能需要考虑调整DRM的触发阈值或关闭自动DRM。不过隐藏参数调整一定要在充分测试后进行,不要因为看到一篇博客就说_gc_policy_time=0。我见过有人图省事把自动DRM完全关闭,结果资源主节点长期和实际访问节点不匹配,又带来了新的跨节点协调开销。
5.3 那些不建议乱调的隐藏参数
RAC的PCM和非PCM底层有大量隐藏参数,名字听起来很诱人,比如_gc_lost_time、_gc_defer_time、_fairness_threshold等。有些老帖子推荐通过调整这些参数来降低gc等待,但我的看法是:如果没有完全理解参数的作用和触发条件,千万不要照搬。
举个例子,_gc_defer_time是用来控制当前块持有者推迟响应远端CR请求的时间,目的是希望请求节点稍后再来,减少CR副本复制。把_gc_defer_time调大,在某些场景下确实能降低gc cr grant的频率,但一旦超过某个阈值,请求节点会等待更久,反而让SQL变慢。这个参数的合适值跟硬件延迟、应用SQL类型关系极大,没有全局最优解。
对于非PCM资源的参数,Oracle更是提供了很多自动管理机制。LMHB(Lock Manager Heartbeat)进程会监控GES健康状态,LMD进程负责处理全局锁请求。真遇到GES相关进程异常时,不要急着改参数,先查alert.log和diag目录下lmd、lmon的痕迹。大多数资源争用问题不是靠参数解决,而是靠SQL优化、锁粒度调整、或者降低并发。
真正建议你规范化的是这几条基本项:
- 私网网卡绑定和MTU最好在安装前测好,不要在出问题时才想起来。
- SQL和索引设计要尽量减小热块争议范围,避免高频更新同一个数据页。
- 数据库的
db_block_size在RAC中不会随便改变,PCM的传输代价和块大小成正相关,这从侧面说明表设计的重要意义。 - 保留足够的undo表空间,让CR构造和Past Image管理不至于因为undo不够而额外阻塞。
6. RAC性能排查先分清PCM还是非PCM
6.1 先看事件命名,再看全局协调
我在处理了太多RAC性能问题之后,形成了一个条件反射:拿到等待事件,第一件事不是看SQL,也不是看参数,而是快速把等待事件分成PCM和非PCM两类。
看到gc cr request,我脑子里出现的是网络传输、LMS进程、数据块状态、GRD更新这些画面。看到gc cr block lost,我直接去看私网有没有丢包、是不是有防火墙在扫描集群通信端口。看到enq: TX - row lock contention,我会立刻去查阻塞会话,看它在等什么、事务开了多久。看到library cache lock,我会先查最近一小时有没有DDL、有没有打补丁后出现大量无效对象。
这样分类有个额外的好处:你不会被别人的判断带走。曾经有客户拿着AWR报告跟我说“我们RAC gc等待高,应该是私网不好”,但我看事件其实大都是enq: HW - contention,跟私网关系不大,最后定位是频繁批量插入导致段高水位扩展争用。如果当时顺着“私网慢”的方向去换网卡,问题根本解决不了。
6.2 GDS不是RAC的分布式扩张版
还有一个经常出现的概念混淆,就是有人问GDS能不能让两个RAC集群之间也做类似PCM的融合。GDS是Oracle的一个负载均衡和可用性框架,它可以在多个Oracle数据库之间分发请求,也可以和Data Guard结合做读负载扩展。但GDS本身并不是底层缓存融合协议,它不会在两个独立集群的数据块缓存之间建立GCS关系。两个RAC集群如果要保持数据一致,常规手段仍然是日志级同步,例如Data Guard,并不是把PCM资源扩展到另一个集群。
换句话说,PCM和非PCM资源的作用域只在一个数据库集群内部。你在节点1看到的某个块,和节点2上的同一个逻辑块,永远由同一个GCS协调;如果想让另一个RAC集群也看到同一个逻辑块,那需要底层复制方案做到数据库级别,而不是靠集群内部的消息机制。
6.3 最后分享一个排障习惯
最后说一个我自己的小习惯:每次处理完这类问题,我都会把当时的等待事件、对应的SQL、blocking会话状态和最终结论记下来。RAC看似复杂,但常见的PCM和非PCM资源等待,种类并没有你想象中那么多。积累十来次之后,你会发现在RAC里排查性能问题就像听一场熟悉的交响乐:不用整场都盯紧,只要某个声部一跑调,你立刻就知道是哪件乐器出了问题。
就拿最常见的一句口头禅收尾:遇到RAC的等待,先问自己一句——这个等待是在传数据块(PCM),还是在等队列锁(非PCM)?把这一句话想明白,后面所有排查动作都不会跑偏。希望这篇拆解能帮你少走一点我曾经走过的弯路,也让你下次听到“内存融合”这个名字时,想到的不再是一个玄乎的词,而是一套可以观察、可以分析、可以优化的机制。
