iostat能看磁盘设备级io繁忙度(如%util、await),不能看sql、innodb状态或具体io来源;需用-x -k -d 1组合参数,避免误读svctm、忽略单次io大小。

iostat 能看出什么,不能看出什么
iostat 是系统级磁盘 IO 快照工具,它不追踪 SQL、不关联 InnoDB 状态,只告诉你“设备层面读写有多忙”。如果你看到 %util 持续接近 100% 或 await 显著升高(比如 >20ms),说明磁盘响应已成瓶颈——但无法区分是慢查询刷脏页、还是备份在拷贝数据、或是日志写满导致阻塞。
常见误判场景:
- 误把
svctm当真实延迟(该字段在现代内核已失效,应忽略) - 只看
r/s和w/s总量,却没结合rkB/s和wkB/s判断单次 IO 是否过大(例如大量 128KB 写入比小块写更易压垮 SSD 随机写能力) - 未用
-x -k -d 1参数组合,漏掉扩展指标和 KB 单位,导致数值理解偏差
InnoDB 刷脏页时机与关键参数联动
InnoDB 不会等事务提交才写盘,而是靠后台线程异步刷脏页。但刷得太慢会堆积 buffer pool,刷得太急又会集中打爆磁盘。核心控制点在三个参数协同:
-
innodb_io_capacity:设为磁盘随机写 IOPS 的 50%~75%,SSD 常设 1000–4000;机械盘建议 100–200。设太高会导致刷页过猛,反而抬高await -
innodb_io_capacity_max:突发刷页上限,建议设为innodb_io_capacity的 2 倍,避免 checkpoint lag 过大触发紧急刷盘 -
innodb_adaptive_flushing:必须开启(默认 ON),否则无法根据 redo log 增长速率动态调节刷页节奏,容易在大事务后出现 IO 尖峰
错误配置示例:innodb_io_capacity = 8000 在一块标称 5000 IOPS 的 NVMe 上,实际因队列深度/锁竞争导致有效吞吐反而下降 30%。
如何确认是刷盘策略问题,而不是 SQL 或索引问题
真正由刷盘引发的 IO 瓶颈,通常伴随这些特征:
-
SHOW ENGINE INNODB STATUS\G中FILE I/O部分显示pending normal aio reads/writes长期非零,且log i/o's done数值远高于buffer pool i/o's done -
pt-ioprofile(或perf record -e block:block_rq_issue)显示大量write请求目标是ib_logfile*或ibdata1,而非表空间文件 - 慢查日志里没有明显高耗时 UPDATE/INSERT,但
Threads_running经常卡在 50+,Innodb_buffer_pool_wait_free持续增长
这时候调优重点不是加索引,而是压低脏页生成速率(比如拆分大事务)+ 放宽刷盘压力(调整上述三个参数)+ 确保 innodb_flush_method=O_DIRECT(避免 double buffering 加重内存和 IO)。
磁盘队列深度与 innodb_write_io_threads 的匹配
MySQL 并发写能力受限于两个层面:InnoDB 层的 innodb_write_io_threads(默认 4),和底层设备的队列深度(如 NVMe 常为 64,SATA SSD 多为 32)。如果线程数远小于队列深度,IO 提交效率被人为卡住;如果线程数过多,又会加剧 mutex 争用。
- SSD/NVMe 场景:可将
innodb_write_io_threads提到 8–16,配合innodb_read_io_threads同步调整 - 机械盘场景:保持默认 4 即可,再多线程只会增加寻道抖动
- 必须同步检查
/sys/block/nvme0n1/queue/nr_requests(或对应设备路径),确保它 ≥innodb_write_io_threads × 2,否则内核层就截断了请求
一个常被忽略的细节:innodb_write_io_threads 修改后必须重启 mysqld 才生效,热加载无效。










