NUMA架构把CPU和内存划分为多个节点,每个CPU访问本节点内存的速度最快。Oracle数据库的共享内存模型对节点间延迟非常敏感,若后台进程和服务器进程跨节点访问SGA、PGA,性能会明显下降。因此,在物理机或未做严格隔离的虚拟化环境中,只靠Oracle默认参数往往不够,需要显式绑定CPU和内存。

NUMA节点与Oracle内存访问的底层关系
在SMP对称多处理结构中,所有CPU通过同一条总线访问物理内存,访问延迟基本一致。NUMA则把处理器和本地内存直接相连,多个节点之间通过高速互联通信。比如一台两路服务器,节点0的CPU访问节点0上的内存只需几十纳秒,而访问节点1上的内存可能超过一百纳秒,具体差距取决于平台和负载。对于Oracle这种大量使用共享内存的应用,远端访问比例升高后,buffer cache命中率即使很高,逻辑读延迟也会被拉长,因为每次读取都要走节点间总线。
Oracle实例启动后会把SGA映射到系统中,Linux默认的内存分配策略通常是节点本地优先,但SGA往往很大,可能被分配到多个节点。数据库进程由操作系统调度,如果没有绑核约束,进程可能被迁移到其他节点。一旦进程与内存不在同一节点,后续大量的buffer cache访问都变成远端访问。也就是说,SQL执行计划没有变化,buffer cache命中率也正常,但响应时间却明显上升,这往往是NUMA引发的隐藏开销。
Oracle在某些版本提供了隐含参数 _enable_NUMA_support 来控制SGA在NUMA节点上的分布行为。将该参数设置为TRUE后,数据库会尽量按节点分配和使用本地SGA子池,减少跨节点引用。但是这个参数不能替代操作系统层面的CPU绑定,它主要影响SGA的布局,进程调度的漂移问题仍需要cgroup或numactl解决。
查看NUMA拓扑与Oracle会话分布
配置之前先要摸清硬件拓扑和当前进程的内存分布。Linux提供numactl和lscpu两个常用工具。numactl --hardware会列出可用节点、每个节点的CPU列表和内存大小。lscpu则能显示NUMA节点数量、Socket数量以及每个Core对应的物理位置。如果系统里看不到多个节点,可能需要检查BIOS设置,通常要启用Node Interleaving之外的NUMA模式。
numactl --hardware lscpu | grep -E "NUMA|Socket|Core" ls /sys/devices/system/node/node*
接下来要确认Oracle进程落在哪些CPU和内存节点。可以通过 /proc 文件系统查看某个进程的允许CPU列表和内存节点列表。比如数据库写进程DBW0的PID,可以执行以下命令。
DBW_PID=$(pgrep -f ora_dbw0 | head -n 1) grep -E "Cpus_allowed_list|Mems_allowed_list" /proc/$DBW_PID/status numastat -p $DBW_PID
如果Cpus_allowed_list覆盖了所有CPU,Mems_allowed_list也是全部节点,说明当前没有做绑核。numastat输出中的foreign字段表示远端访问的比例,数值越大越需要干预。此外,Oracle自身提供的GV$OSSTAT视图中可以查询NUMA相关统计,不同平台统计项有差异,可作为辅助参考。
SELECT INST_ID, STAT_NAME, VALUE FROM GV$OSSTAT WHERE STAT_NAME LIKE '%NUMA%' ORDER BY INST_ID, STAT_NAME;
数据库绑核与内存绑定的配置方法
数据库绑核建议优先使用cgroup/cpuset,它能同时限制CPU和内存节点,并且对Oracle所有后台进程和后续派生进程统一生效。先创建一个cpuset子系统目录,指定允许使用的CPU范围以及内存节点编号。例如一台两节点服务器,想让实例只运行在节点0的0到15号CPU上,只使用节点0内存,可以这样设置。
mkdir -p /sys/fs/cgroup/cpuset/oracle_db echo "0-15" > /sys/fs/cgroup/cpuset/oracle_db/cpuset.cpus echo "0" > /sys/fs/cgroup/cpuset/oracle_db/cpuset.mems echo 0 > /sys/fs/cgroup/cpuset/oracle_db/cpuset.memory_migrate for p in $(pgrep -f ora_); do echo $p > /sys/fs/cgroup/cpuset/oracle_db/tasks; done
以上命令会把当前所有Oracle后台进程放入cgroup。新启动的服务器进程由监听器派生,只要父进程在cgroup中,子进程也会继承限制。如果希望在数据库启动前就生效,可以修改操作系统服务脚本,在启动数据库前先创建cgroup,并使用cgexec启动实例,或者在Oracle集群件中配置资源启动脚本。需要注意的是,内存节点编号不能写错,如果指定了CPU节点0却只允许内存节点1,反而会制造大量远端访问。
另一种方式是使用numactl工具直接包装启动命令。比如手动启动SQL*Plus并使用节点0的CPU和内存:
numactl --cpunodebind=0 --membind=0 sqlplus / as sysdba
但这种方式只对当前进程及其子进程生效,适合临时诊断或演示,不适合管理整个实例。对于SGA很大、需要跨多个节点的场景,可以使用numactl --interleave=all启动实例,让内存页均匀分布在所有节点上,避免单个节点内存压力过大。交错分配减少局部热点,但没有绑核,仍需配合cpuset限制调度。
Oracle的隐含参数 _enable_NUMA_support 可以在SGA层面影响NUMA行为。通过ALTER SYSTEM设置在SPFILE中,重启后生效。该参数不一定在所有平台都受支持,正式环境应在Oracle Support指导下测试,因为隐含参数行为可能随补丁变化。
ALTER SYSTEM SET "_enable_NUMA_support"=TRUE SCOPE=SPFILE;
另外,如果数据库运行在容器或虚拟化环境,需要确认虚拟化层是否正确透传NUMA拓扑。KVM或VMware如果只给虚拟机一个平坦的vNUMA,绑核看到的是虚拟节点,效果会打折扣。这种情况应在宿主机上先绑物理CPU和内存,再映射给虚拟机,客户机内部再做二次绑定。
验证绑核效果与常见误区
配置完成后需要用实际数据验证是否真正减少了远端内存访问。numastat -p可以查看单个进程的numa_miss和numa_foreign。对Oracle的多个关键进程,可以汇总统计:
for pid in $(pgrep -f ora_dbw0; pgrep -f ora_lgwr; pgrep -f ora_smon); do echo "PID $pid" numastat -p $pid | tail -n 2 done
如果foreign值仍然很高,说明进程承载的内存页还在远端节点,或者进程被调度到了非绑定节点。还可以查看 /proc/pid/numa_maps 文件,每一行对应一个虚拟内存区域,其中N0、N1等字段表示该区域分配在各个节点的页数。根据这些信息能知道SGA区域是否被错误地放在某一边。
绑核时常见的第一个误区是把所有Oracle进程都塞进一个很小的CPU子集或单个NUMA节点。节点内CPU数量有限,高峰期CPU队列会变长,反而比跨节点调度更慢。第二个误区是只绑定CPU不绑定内存,或者只绑定内存不约束CPU,结果仍会出现节点错配。第三个误区是认为NUMA绑定可以一劳永逸,实际上数据库负载变化、实例重启、服务器补丁升级后,cgroup或启动脚本可能失效,需要纳入配置管理和监控。
还有一点,如果系统已经启用Oracle自动内存管理或大页,调整NUMA策略后需要使用与之前相同的大页数量和内存限制,避免出现大页碎片或SGA分配失败。对于Exadata一体机或经过Oracle认证的集成系统,厂商已经做了NUMA调优,不建议自行修改隐藏参数。
Oracle NUMA数据库绑核NUMA内存策略修改时间:2026-10-06 22:06:27