根本原因是numa默认localalloc策略导致buffer pool内存碎片化分布、线程跨节点调度及本地内存耗尽后触发隐性swap,三者形成负向闭环;需同步优化内存绑定、线程亲和性与回收策略。

MySQL在NUMA架构服务器上出现严重性能不均衡,根本原因不是配置错误或SQL写法问题,而是默认内存分配策略与多线程调度行为在NUMA拓扑下产生了系统性错配——Buffer Pool被拆散、线程被乱派、本地内存被耗尽后被迫跨节点访问,延迟直接翻倍。
MySQL的Buffer Pool如何被NUMA“撕碎”
InnoDB Buffer Pool是连续大块内存,但Linux默认使用localalloc策略:每个线程申请内存时,优先从它当前运行的CPU所在NUMA节点分配。MySQL启动时主线程可能在Node 0,但后续连接线程可能被调度到Node 1、Node 0交替运行,导致Buffer Pool内存碎片化分布在多个节点。
-
numastat -p $(pgrep mysqld)显示other_node>25%,说明Buffer Pool至少1/4内存不在主线程所在节点 - 一次SELECT扫描索引页,若页在Node 1而执行线程在Node 0,就要走QPI总线取数据,延迟从100ns涨到250ns+
- InnoDB的LRU链表操作、脏页刷盘等内部动作也会因内存跨节点而频繁触发缓存同步开销
线程调度与CPU亲和性失控
MySQL默认不限制线程绑定,Linux CFS调度器会把连接线程动态迁移到空闲CPU,而这些CPU可能属于不同NUMA节点。结果就是:同一连接的多个语句在不同节点执行,反复触发远程内存访问。
- 用
pidstat -t -p $(pgrep mysqld) 1 5观察migrate字段,若每秒迁移次数 >3,说明线程在节点间“漂移” - 即使启用了
innodb_numa_interleave=ON(MySQL 8.0.27+),也只是缓解Buffer Pool分配,不控制线程位置 -
taskset -c 0-11 mysqld强制绑定后,numastat -p的numa_hit通常能从70%升至92%以上
本地内存耗尽触发“隐性Swap”
当某个NUMA节点(如Node 0)内存被MySQL Buffer Pool占满,而其他节点(Node 1)仍有大量空闲时,Linux不会自动将部分内存迁移过去。反而可能触发kswapd在Node 0内回收内存,甚至将匿名页换出到磁盘——尽管整机内存充足。
- 现象:
free -h显示总内存充足,但cat /proc/meminfo | grep -i "swap\|reclaim"中SwapCached或PageOut持续增长 - 关键指标:
numastat -c中 Node 0 的numa_free接近0,而 Node 1 的numa_free>30GB - 临时解法:
echo 1 > /proc/sys/vm/zone_reclaim_mode关闭单节点强制回收(需配合--membind使用)
为什么numactl --interleave=all有时更糟
对MySQL这类有强局部性的应用,--interleave=all看似公平,实则破坏了数据与计算的物理邻近性。Buffer Pool页被轮询分配到所有节点,每次访问都可能命中远端内存,且无法利用本地NUMA节点的内存带宽峰值。
- 适用场景仅限于:工作集远超单节点内存 + 访问完全随机(如某些OLAP分析)
- MySQL OLTP负载具有明显局部性:热点表、索引、undo log集中在少数内存页,应尽量让它们与活跃线程共处一节点
- 正确做法是:先用
numactl --cpunodebind=0 --membind=0锁定主节点,再通过--preferred=0允许必要时 fallback 到其他节点
真正卡住性能的,往往不是某一行配置写错,而是Buffer Pool分配、线程调度、内存回收这三件事在NUMA环境下形成了负向闭环——一个环节没对齐,其余两个就会放大问题。调试时必须同时看 numastat、pidstat -t 和 /proc/meminfo,缺一不可。











