根本原因是InnoDB缓冲池与OS内存管理冲突:innodb_buffer_pool_size过大、swappiness未调低、memlock未启用三者缺一都会导致mysqld被换出到Swap。

MySQL查询大数据量时Swap飙升,根本不是数据量本身的问题,而是InnoDB缓冲池和操作系统内存管理策略冲突导致的——innodb_buffer_pool_size设得太大、swappiness没压住、memlock没锁死,三者缺一都会让mysqld进程被内核悄悄换出。
为什么查4000万行就触发Swap,而不是CPU或IO瓶颈
现象是“CPU很低但SELECT 1超时”“慢查询日志里全是Waiting for table flush”,这不是SQL慢,是页在磁盘和内存之间反复搬运。InnoDB以为数据还在buffer pool里,实际已被OS踢进Swap,每次访问都触发毫秒级磁盘读,响应时间从微秒跳到百毫秒以上。
- 用
vmstat 1看si(swap-in)和so(swap-out)持续 > 0,基本锁定Swap问题 -
free -h里SwapUsed上涨 +Availableused高更危险 - 别信“清缓存能腾内存”:
echo 3 > /proc/sys/vm/drop_caches只会让MySQL更卡——刚清完cache,它立刻要从磁盘重载热数据,加剧swap压力
innodb_flush_method=O_DIRECT必须配对生效,否则白设
innodb_flush_method=O_DIRECT只绕过OS page cache对.ibd文件的缓存,但redo log默认仍走OS buffer。结果是buffer pool和log buffer在内存里抢页,触发频繁page reclaim,间接推高swap倾向。
- 确认my.cnf中[mysqld]段明确写了:
innodb_flush_method=O_DIRECT(不是O_DSYNC,后者不绕cache) - 启动后执行
SHOW VARIABLES LIKE 'innodb_flush_method',输出必须是O_DIRECT,空值或async_unbuffered都算失败 - 启用后
innodb_buffer_pool_size要下调:原来设80%,现在最多70%,否则启动直接报OOM
swappiness=1只是减缓,memlock=unlimited才是根治
swappiness=0不等于禁用Swap——OOM killer或内存严重不足时,mysqld照样被换出。memlock才是让MySQL进程免疫Swap的硬控制。
- 在
/etc/security/limits.conf加两行:mysql soft memlock unlimited和mysql hard memlock unlimited - my.cnf中加:
innodb_use_sys_malloc=0(尤其用了jemalloc时) - 必须用root启动mysqld(systemd下检查
LimitMEMLOCK是否为infinity) - 验证是否生效:
cat /proc/$(pgrep mysqld)/status | grep -i "cap",CapBnd字段必须含cap_ipc_lock
NUMA架构下不配innodb_numa_interleave,memlock可能白开
多路Xeon服务器上,若MySQL启动时未指定NUMA策略,memlock锁住的内存可能被分配到远端节点,跨NUMA访问延迟高,内核仍可能换出页面。
- MySQL 5.6.27+ 直接在my.cnf中加:
innodb_numa_interleave=ON - 旧版本需在启动脚本里用
numactl --interleave=all包裹mysqld - 不配这个,即使
memlock成功,Swap仍可能缓慢上涨,尤其在写密集场景
真正难的是三者协同:memlock锁内存、O_DIRECT断cache争抢、innodb_numa_interleave保本地分配。少一个,Swap就可能在半夜流量高峰时悄然回升——而监控告警往往只报“慢查询”,不会告诉你页正在磁盘和内存间流浪。


















