mysql全量逻辑备份触发大量文件读取,填满node 0的inactive_file页,导致free+file≤high水位,内核强制swap匿名页,引发mysqld被换出。

备份时mysqld进程被内核换出,不是因为内存不够,而是因为file cache挤占了zone watermarks
MySQL全量逻辑备份(如 mysqldump 或 mydumper)本身不直接申请大量堆内存,但会触发大量文件读取——尤其是读取 .ibd 文件和系统表空间。这些读操作被内核计入 PageCache,迅速填满 node 0 的 inactive_file 页,导致 free + file 水位被突破。此时内核启动全局回收,因 <code>file + free 成立,强制将匿名页(即 <code>mysqld 的 buffer pool 和线程栈)换入 Swap。
典型现象:vmstat 1 中 si(swap-in)不高但 so(swap-out)持续跳动;free -h 显示 buff/cache 占用飙升、available 骤降;mysqld 进程 RSS 未涨,但 SwapUsed 持续上升。
- 这不是 MySQL 自身内存泄漏,也不是
innodb_buffer_pool_size设得太大 - NUMA 架构下尤其明显:备份读取集中在 node 0,而
mysqld内存分配默认也在 node 0,cache 和 anon 页在同一个 zone 冲突 -
swappiness=1仅降低倾向,无法阻止水位触底后的强制 swap-out
为什么innodb_flush_method=O_DIRECT单独启用对备份期间Swap无效
innodb_flush_method=O_DIRECT 只影响 .ibd 文件的写路径,绕过 PageCache;但备份是纯读场景,它完全不生效。更关键的是:备份过程不写 redo log,所以 log buffer 路径也不参与竞争。真正抢内存的是内核的 page cache 分配逻辑,和 InnoDB 刷盘策略无关。
- 验证方式:备份前执行
echo 3 > /proc/sys/vm/drop_caches,再跑一次备份——若so明显下降,就证实是 cache 挤压导致 - 不要误以为改了
innodb_flush_method就能解决备份 swap 问题;它针对的是写密集型负载(如批量导入),不是读密集型 - 如果同时启用了
innodb_use_sys_malloc=0(如 jemalloc),还可能因 malloc arena 分配策略加剧 NUMA 不平衡
NUMA节点不均衡是备份高Swap的隐藏开关
在双路 Xeon 等 NUMA 服务器上,mysqld 启动时默认绑定到 node 0,而备份工具(如 mysqldump)通常无 NUMA 绑定,其读取的 page cache 也落在 node 0。这造成 node 0 的 zoneinfo 中 nr_inactive_file 暴涨、nr_free_pages 锐减,触发该 node 独立回收,且优先换出本 node 的匿名页(即 mysqld 的 buffer pool)。
- 检查命令:
numastat -p $(pgrep mysqld)查看各 node 内存分布;cat /sys/devices/system/node/node*/meminfo | grep -E "Node|Inactive(file)|Free"对比水位 - 临时修复:
numactl --interleave=all mysqld_safe --defaults-file=/etc/my.cnf & - 长期方案:MySQL 5.7.9+ 可配
innodb_numa_interleave=ON,但必须配合memlock和swappiness=1才有效,否则 interleave 无意义
备份期间Swap升高的真正可控变量只有三个
所有其他调优(如调大 sort_buffer_size、关 query_cache)对备份期间的 Swap 压力几乎无影响。真正起作用的只有:
-
vm.swappiness=1:防止非水位触发的“提前换出”,但无法阻止水位触底 -
memlock unlimitedfor mysql user:确保mysqld的匿名页被标记为MCL_FUTURE,内核在回收时跳过这些页(需 root 启动) -
numactl --interleave=all或innodb_numa_interleave=ON:把 cache 和 anon 页压力摊到多个 node,避免单 node 水位击穿
这三个必须同时满足,缺一不可;只做其中一两个,so 仍会在备份中段突然飙升。最易被忽略的是 memlock 权限未生效——检查 prctl -r $PID | grep "cap" | grep lock,若无 cap_ipc_lock,说明 limits.conf 没 reload 或 mysqld 未用 root 启动。











