mysql磁盘io瓶颈需综合数据库内部行为与系统i/o响应:关注innodb_buffer_pool_reads是否持续>100、w_await是否超10ms(ssd)或20ms(nvme)、aqu-sz是否>4(nvme),并用performance schema查sum_disk_reads定位高频小查询,结合临时表溢出和排序区配置协同分析。

MySQL 磁盘 I/O 瓶颈不能只靠“感觉”——iostat 显示高 %util 或 w_await,不代表就是 MySQL 的锅;反过来,SHOW ENGINE INNODB STATUS 里缓冲池命中率 99.5%,也掩盖不了某条慢查询正疯狂扫磁盘。真问题藏在「数据库内部行为」和「系统 I/O 响应」的交界处。
怎么看 InnoDB 缓冲池是否真扛得住
缓冲池命中率不是万能指标,尤其在写多读少或冷热混合场景下容易误判。关键要看它背后的真实页读压力。
-
Innodb_buffer_pool_reads(每秒物理读页数)持续 > 100,说明即使命中率 98%,仍有大量热数据没进缓存,或索引覆盖不足导致频繁回表 -
Innodb_buffer_pool_read_requests高但Innodb_buffer_pool_reads也高,大概率是全表扫描或ORDER BY触发了文件排序(Using filesort) - 执行
SHOW ENGINE INNODB STATUS\G后,在BUFFER POOL AND MEMORY段里找Pages read和Pages created,若前者远高于后者,说明读放大严重,而非写入引发的刷盘 - 注意:MySQL 8.0.22+ 支持
EXPLAIN FORMAT=JSON中的disk_reads字段,可直接看到某条语句预估的磁盘读页数,比慢日志更早暴露 I/O 成本
为什么 iostat 的 %util 对 SSD/NVMe 没参考价值
%util 是基于传统 HDD 队列饱和度设计的,对并行能力强的 SSD/NVMe 完全失真。你看到 %util = 35%,实际 I/O 队列可能已堆积上百请求。
- 真正该盯的是
r_await和w_await:超过 10ms(SSD)或 20ms(NVMe)就需警惕,说明请求在队列中等待过久,不是设备慢,是并发压过了调度能力 -
aqu-sz(平均队列深度)> 4(NVMe)或 > 1(SATA SSD)时,基本确认 I/O 调度已成瓶颈,此时iostat -x 1的svctm已不可信,别再看它 - 用
iotop -oP -p $(pgrep mysqld)直接过滤 mysqld 进程,观察其IO>列是否持续 > 50MB/s —— 若远超磁盘标称顺序写带宽(如 NVMe 标称 3GB/s,但单进程写不过 500MB/s),说明有大量小随机 IO 在拖慢吞吐
如何用 Performance Schema 定位“悄悄吃 I/O”的 SQL
慢查询日志只抓“慢”,但很多高频小查询(比如每秒上千次的 SELECT COUNT(*))不慢却耗 I/O,它们在 events_statements_summary_by_digest 里才露头。
- 查磁盘读最多的前 10 条语句:
SELECT DIGEST_TEXT, SUM_TIMER_WAIT, SUM_ROWS_EXAMINED, SUM_DISK_READS FROM performance_schema.events_statements_summary_by_digest WHERE SUM_DISK_READS > 0 ORDER BY SUM_DISK_READS DESC LIMIT 10;
-
SUM_DISK_READS非零但SUM_ROWS_EXAMINED很小,典型是索引失效后走主键扫描(比如WHERE JSON_CONTAINS(meta, '"active"')) -
SUM_ROWS_EXAMINED大但SUM_DISK_READS更大,说明缓冲池根本没缓住,可能是innodb_buffer_pool_size设置太小,或数据访问模式极度离散(如 UUID 主键) - 记得先开启相关仪器:
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'stage/innodb/%';,否则SUM_DISK_READS恒为 0
临时表和排序区溢出才是最隐蔽的 I/O 杀手
很多 DBA 只盯着 Created_tmp_disk_tables,却忽略它只是“结果”。真正触发溢出的是内存配置与查询特征的错配。
-
tmp_table_size和max_heap_table_size必须一致,否则以较小值为准;设为 64M 是常见陷阱——现代 OLTP 查询动辄 JOIN 三张百万级表,64M 内存根本不够建中间结果集 -
sort_buffer_size是 per-connection 参数,设太大(如 4M)会导致连接数一多就 OOM;设太小(如 256K)又让ORDER BY必然落盘。建议从 512K 起调,配合EXPLAIN看Extra是否还有Using filesort -
Created_tmp_disk_tables / Questions比值 > 0.05,基本可断定排序/分组类查询设计不合理,优先检查GROUP BY字段是否建了联合索引,而不是盲目调大内存参数 - 注意:MySQL 8.0+ 的
internal_tmp_mem_storage_engine = TempTable默认启用,它比老式MEMORY引擎更省内存,但一旦溢出仍会写磁盘,且不计入Created_tmp_disk_tables计数——得看Performance Schema的events_waits_summary_global_by_event_name中wait/io/file/innodb/innodb_data_file的等待次数
真正卡住 I/O 的,往往不是单个大操作,而是多个小操作在缓冲池、排序区、临时表之间反复横跳,把随机读放大好几倍。监控时别只盯一个数字,要连起来看:InnoDB 状态里的页读速率、iostat 的 await、Performance Schema 的 disk_reads、以及慢日志里那些“不慢但高频”的语句——四者对齐,才能揪出那个偷偷拖垮磁盘的家伙。











