MySQL周期性卡顿但CPU低,根本原因是swap频繁换页导致buffer pool热页反复踢出与拉回;需同时闭环调低vm.swappiness、配置memlock锁定内存、确保root权限启动,缺一不可。

MySQL周期性卡顿但CPU很低,基本就是swap在换页——不是SQL慢、不是锁冲突,是buffer pool里的热页被反复踢出又拉回磁盘。单靠调vm.swappiness或加innodb_flush_method=O_DIRECT都压不住,必须三件事同时闭环:内核倾向压低、进程内存锁死、启动权限到位。
怎么确认是swap在周期性抖动
别看top里mysqld的%Cpu,也别查慢日志。直接跑:vmstat 1,盯住si(swap-in)和so(swap-out)两列:
- 单位是KB/s,只要任一值在卡顿时稳定 > 1000,就是swap正在高频搬运页
- 同步跑
free -h,看SwapUsed是否随卡顿周期性上涨(哪怕只涨几十MB) -
swpd > 0+si/so > 0同时成立,才能锁定swap是根因 - 注意:
used内存高不危险,MemAvailable持续低于1G才真警报
为什么只改vm.swappiness=1还是卡
vm.swappiness=1只是降低内核换页倾向,不是开关。它挡不住OOM Killer在内存压力突增时把mysqld整页踢进swap——Buffer Pool的LRU链表瞬间失序,查询延迟就毛刺不断:
- 执行
sudo sysctl vm.swappiness=1后,必须立刻用vmstat 1验证si/so是否掉到个位数 - 永久生效要写进
/etc/sysctl.conf并执行sudo sysctl -p,漏掉这步重启后还是默认60 - 云主机(如Ubuntu 20.04+)还要确认
swapon --show输出为空,否则宿主机设置对容器无效 - 设成0并不安全:内核仍会在OOM前最后一刻启用swap,抖动照旧
memlock没配对等于白干
memlock才是让mysqld真正免疫swap的硬手段,但配置错一点就失效:
- 在
/etc/security/limits.conf加两行:mysql hard memlock unlimited和mysql soft memlock unlimited - 在
my.cnf的[mysqld]段加innodb_use_sys_malloc=0(尤其用了jemalloc时) - 必须由root启动mysqld:systemd下检查
LimitMEMLOCK=infinity是否生效;非root启,prlimit -p $(pgrep mysqld) | grep memlock会显示有限值 - 启动失败常见原因:
innodb_buffer_pool_size设太高(原来80%现在得压到≤70%),或/proc/$(pgrep mysqld)/status里CapBnd不含cap_ipc_lock
O_DIRECT单独开反而加剧swap倾向
innodb_flush_method=O_DIRECT只绕过page cache写.ibd数据文件,但redo log默认仍走OS cache。buffer pool和log buffer在内存里抢页,反而推高page reclaim:
- 必须配合
vm.swappiness=1和memlock才有效,否则是半截子工程 - 验证是否生效:启动后执行
SHOW VARIABLES LIKE 'innodb_flush_method',输出必须是O_DIRECT,不是空或async_unbuffered - 用
strace -e trace=io_submit,write,pread64 -p $(pgrep mysqld)观察是否有write()调用;若有,说明仍在走page cache - NUMA架构下(如多路Xeon),还需配
innodb_numa_interleave=ON或用numactl --interleave=all启动,否则numastat -p $(pgrep mysqld)可能显示numa_miss持续>5%
真正难的不是调参数,而是每个环节都闭环:swappiness值改了、memlock限制加了、MySQL是root启的、prlimit验证过生效、容器里还单独设了--memory-swappiness=1——漏掉任意一个,buffer pool就随时可能被撕开一道口子。


















