4. CPU调度与数据库负载

数据库主机的CPU调度问题,往往不是“CPU不够快”,而是“该跑在某个核上的线程被挪走了”。在RHEL上,数据库进程的响应时间同时受三件事影响:进程优先级(nice值与实时优先级)、调度策略(CFS、SCHED_FIFO、SCHED_RR、SCHED_BATCH、SCHED_IDLE)、以及NUMA亲和性(线程与内存是否落在同一节点)。三者叠加,才会出现“CPU利用率不高但SQL延迟抖动”的典型现象。

一、先理解RHEL的调度层次

RHEL默认使用CFS(完全公平调度器),普通进程的优先级由nice值体现,范围-20到19,数值越小优先级越高。数据库进程通常以普通优先级运行,但Oracle的LGWR、DBWn等后台进程,以及MySQL的InnoDB刷盘线程,对延迟敏感,若与大量批处理进程共享CPU,会被CFS按权重分配时间片,导致提交延迟抖动。

实时调度策略SCHED_FIFO和SCHED_RR优先级为1到99,高于所有普通进程。把数据库关键线程设为实时策略,可以避免被普通负载抢占,但风险是:若实时线程进入忙等,会饿死其他进程,甚至触发soft lockup。因此生产上更稳妥的做法是:用cgroup的cpu.weight做权重隔离,用cpuset绑定核,而不是盲目上实时策略。

二、NUMA亲和性:数据库延迟的隐形杀手

在多路服务器上,CPU和内存被划分为多个NUMA节点。跨节点访问内存的延迟通常是本地访问的1.5到2倍。数据库的共享内存、Buffer Pool、Redo Log Buffer若被分散到多个节点,线程在节点A运行却访问节点B的内存,就会产生远端访问。更严重的是,Linux默认的自动NUMA平衡(Auto NUMA)会在运行中迁移任务和内存,虽然提升吞吐,但可能引入不可预测的延迟抖动。

判断是否存在NUMA问题,可以观察numastat中的numa_miss和numa_foreign,以及/proc/<pid>/numa_maps。若某个数据库进程的numa_miss持续增长,说明它频繁访问远端内存。

数据库线程调度与NUMA亲和性关系图 NUMA Node 0 CPU 0-15 | 本地内存 64GB DBWn 线程 nice=0 | CFS LGWR 线程 nice=-5 | CFS 本地内存访问:延迟 ≈ 80ns Buffer Pool 本地分配,命中率高 NUMA Node 1 CPU 16-31 | 本地内存 64GB 批处理进程 nice=19 | SCHED_BATCH 备份进程 nice=10 | CFS 远端内存访问:延迟 ≈ 140ns 跨节点访问,延迟增加约75% 跨节点 numa_miss ↑ 调度策略与优先级对数据库线程的影响 CFS(默认) 按权重分配,公平但抖动 SCHED_FIFO 实时,不抢占,风险高 SCHED_BATCH 批处理,降低交互延迟 cpuset + nice 绑定核+调优先级,推荐 调优方向:本地化 + 隔离 + 优先级分层 numactl --cpunodebind=0 --membind=0 绑定数据库实例 cgroup cpuset 隔离批处理与数据库核 chrt -f 50 仅用于极关键短线程,需评估风险 关闭 Auto NUMA 或设置 numa_balancing=disable

三、实际调优:从观察到落地

假设一台双路服务器,两个NUMA节点,运行Oracle 19c。观察到AWR报告中log file sync平均等待2ms,但P99达到18ms。检查numastat -p <LGWR_PID>,发现numa_miss为12000,numa_foreign为8000,说明LGWR线程频繁跨节点访问内存。

第一步,确认LGWR所在节点与共享内存分布。使用numactl --hardware查看节点拓扑,用ps -eo pid,psr,comm | grep lgwr查看LGWR当前运行核。若LGWR在Node 1,而SGA主要分配在Node 0,则跨节点访问成立。

第二步,绑定实例到单一节点。对于单实例数据库,推荐将整个实例绑定到一个NUMA节点,避免跨节点内存访问:

numactl --cpunodebind=0 --membind=0 -- /u01/app/oracle/product/19c/dbhome_1/bin/oracle
# 或在启动脚本中设置
export NUMA_BIND="numactl --cpunodebind=0 --membind=0"
$NUMA_BIND $ORACLE_HOME/bin/sqlplus / as sysdba

第三步,隔离批处理负载。将备份、统计信息收集等批处理进程绑定到Node 1,并设置较低优先级:

# 将备份进程绑定到Node 1,nice=19
numactl --cpunodebind=1 --membind=1 nice -n 19 /backup/rman_backup.sh

# 使用cgroup v2限制批处理CPU权重
mkdir -p /sys/fs/cgroup/batch
echo "cpu.weight 10" > /sys/fs/cgroup/batch/cpu.weight
echo $BACKUP_PID > /sys/fs/cgroup/batch/cgroup.procs

第四步,调整关键线程优先级。LGWR、DBWn等线程可适当提高优先级,但不建议直接上实时策略。可通过renice调整:

# 将LGWR的nice值从0调整为-5
renice -n -5 -p $(pgrep -f "ora_lgwr")

# 验证
ps -eo pid,ni,comm | grep lgwr

第五步,关闭Auto NUMA。在数据库主机上,Auto NUMA的自动迁移可能带来延迟抖动。可在内核启动参数中加入numa_balancing=disable,或运行时关闭:

echo 0 > /proc/sys/kernel/numa_balancing
# 永久生效:grubby --update-kernel=ALL --args="numa_balancing=disable"

四、效果验证与判断依据

调优后,重新采集AWR或使用numastat观察。若numa_miss不再增长,log file sync的P99从18ms降至5ms以内,说明NUMA亲和性改善有效。同时观察mpstat -P ALL 1,确认数据库核的%idle稳定,批处理核的%sys不侵占数据库核。

若延迟仍抖动,需检查是否存在CPU频率调节(cpufreq)导致的降频,或irqbalance将中断分散到数据库核。可将数据库核从irqbalance中排除,或设置isolcpus隔离核。注意:isolcpus会将这些核从通用调度中移除,需配合cpuset使用,否则数据库线程无法使用这些核。

关键判断链:延迟抖动 → 检查numastat的numa_miss → 确认线程与内存节点 → 绑定实例到单节点 → 隔离批处理 → 调整关键线程nice → 关闭Auto NUMA → 复测P99。每一步都应有明确的观察指标,避免盲目调参。
风险提示:实时调度策略(SCHED_FIFO/SCHED_RR)在数据库主机上应谨慎使用。若LGWR被设为实时且进入忙等,可能阻塞其他关键进程,导致实例挂起。生产环境优先使用cpuset隔离与nice调整,实时策略仅用于经过充分测试的短时关键线程。

总结来说,CPU调度对数据库响应时间的影响,核心在于“本地化”与“隔离”。NUMA亲和性解决内存访问延迟,调度策略与优先级解决CPU争用。两者结合,才能把P99延迟从不可控的抖动,收敛到可预期的范围。