MySQL写入慢主因是宿主机I/O调度器(cfq/bfq)延迟Redo Log fsync,需在宿主机切换调度器(NVMe设none、SATA SSD设noop或deadline),调优innodb_log_file_size和log_buffer_size,并重启MySQL进程生效。

MySQL在Docker中写入慢,先看宿主机I/O调度器是不是cfq或bfq
不是MySQL配置错了,也不是容器没给够资源——根本卡点常在宿主机块设备的I/O调度器上。Docker本身不干预I/O调度,但cfq(旧内核)或bfq(较新默认)会把MySQL频繁的小size、高频率fsync()请求合并排队,导致Redo Log落盘延迟飙升到20ms+。
确认方法很简单:
- 在宿主机执行
cat /sys/block/*/queue/scheduler,找到MySQL数据盘对应设备(如nvme0n1或sda),看输出里当前激活的是不是[cfq]或[bfq] - 进MySQL执行
SHOW ENGINE INNODB STATUS\G,检查Log sequence number和Log flushed up to差值是否持续扩大(>10MB说明Redo积压) - 用
iostat -x 1观察await是否长期 >5ms,同时%util不高但svctm偏高——这是调度器引入延迟的典型信号
改I/O调度器必须在宿主机操作,且不能一刀切设none
容器里改/sys/block/xxx/queue/scheduler无效,必须在宿主机对应块设备上操作。但盲目echo none > ...会出问题:
-
NVMe SSD:设为none(禁用调度),命令:echo none > /sys/block/nvme0n1/queue/scheduler -
SATA SSD:优先noop,次选deadline;cfq/bfq必须避开 -
机械盘(不推荐跑生产MySQL):保留deadline,但要接受天然IO延迟 - 注意:
/var/lib/docker所在分区和MySQL数据目录(如/var/lib/mysql挂载点)不能是同一物理设备,否则容器元数据IO和MySQL Redo IO会互相干扰
只调调度器不重启MySQL进程,等于白调
InnoDB在启动时探测底层设备的fsync行为并缓存能力,运行中改调度器对已加载的InnoDB引擎无感知。所以:
- 改完
/sys/block/xxx/queue/scheduler后,必须重启MySQL进程(不是docker restart,而是kill -15主进程再拉起,或docker exec -it mysql kill -15 1) - 验证是否生效:用
pt-stalk抓取fsync()系统调用耗时,从20ms降到1ms以内才算成功 - 同步调整MySQL参数才能放大收益:
innodb_log_file_size建议设为1G~2G(避免小日志触发频繁checkpoint);innodb_log_buffer_size提到64M(减少buffer flush频次);禁用innodb_flush_method=O_DSYNC,改用O_DIRECT
Docker内存限制过低会导致InnoDB Buffer Pool被压缩
即使宿主机有32GB内存,如果容器只分配了1GB(--memory=1g),innodb_buffer_pool_size设再大也无效——mysqld启动时会自动向下裁剪到可用内存的75%左右,实际Buffer Pool可能只剩600MB,大量磁盘随机读随之而来。
- 检查真实分配量:
docker inspect mysql | grep -A 5 Memory,确认HostConfig.Memory值 -
innodb_buffer_pool_size应设为容器内存上限的50%~75%,例如容器--memory=4g,则配2G~3G - 别只看
docker stats的瞬时内存占用:InnoDB Buffer Pool是预分配+常驻的,stats里看到的RSS偏低,不代表没生效 - 若用
docker-compose,确保deploy.resources.limits.memory和MySQL配置里的innodb_buffer_pool_size数值逻辑自洽
真正容易被忽略的是:I/O调度器切换 + MySQL参数调优 + 进程重启,这三件事必须闭环完成。少一步,性能就卡在原地。











