SQL IO 成为瓶颈时如何处理?

确认磁盘IO是瓶颈需满足:MySQL iowait持续>10%且iostat显示某盘%util≥95%、await>10ms(SSD应<1ms);缓冲池命中率<95%、Innodb_data_reads突增、iotop显示mysqld刷写、INNODB STATUS有大量pending reads、Created_tmp_disk_tables持续上涨。

怎么确认磁盘 IO 真是瓶颈?

别一慢就猜 IO,得用数据说话。最直接的信号是:MySQL 进程的 iowait 持续高于 10%,同时系统级 iostat -x 1 显示某块盘 %util 长期 ≥95%、await 超过 10ms(SSD 应该在 1ms 内)。这时再查 MySQL 内部指标:Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests 算出的缓冲池命中率若低于 95%,说明大量请求被迫落盘;Innodb_data_reads 每秒突增几千甚至上万,基本坐实物理读爆炸。

  • iotop -o 能立刻看到是不是 mysqld 在刷写,而不是备份或日志轮转抢了 IO
  • SHOW ENGINE INNODB STATUS\G 里 “FILE I/O” 段若出现大量 pending reads,代表后台线程已跟不上请求节奏
  • Created_tmp_disk_tables 持续上涨,说明排序/分组被迫写磁盘,不是内存能扛住的

哪些 SQL 最容易把磁盘 IO 打爆?

不是慢查询才危险,而是“扫得多、写得勤、临时多”的语句——它们不一定会超 long_query_time,但每秒都在制造随机 IO。

  • 全表扫描(EXPLAINtype=ALL)且 rows > 10 万:哪怕只执行一次,也可能触发数 GB 物理读
  • ORDER BY + LIMIT 深度分页(如 LIMIT 100000,20):MySQL 得先扫完前 10 万行再取 20 行,中间全走磁盘
  • GROUP BYDISTINCT 大结果集:内存不够时自动写 /tmp,IO 峰值常被忽略
  • 批量 INSERT/UPDATE 未分批:一次塞 10 万行,会密集刷 redo log 和脏页,IO 曲线像心电图

开启 log_queries_not_using_indexes = ON,比只依赖慢日志更能揪出这类“不慢但很脏”的语句。

配置和硬件上怎么快速止损?

优化 SQL 是治本,但业务压过来时得先稳住 IO。优先动这几项:

  • innodb_buffer_pool_size 设为物理内存的 70%~80%:不够大,热数据留不住,等于天天重读磁盘
  • innodb_flush_log_at_trx_commit 从 1 改成 2:单机可靠性可接受时,能砍掉约 70% 的 redo log 同步 IO(注意:崩溃可能丢 1 秒事务)
  • redo log 文件(ib_logfile*)和 binlog 单独放到 SSD 分区,别和数据文件挤在一起
  • 检查 innodb_log_file_size:太小(如默认 48MB)会导致频繁 checkpoint,产生大量脏页刷盘;建议总和设为 1~2GB(需停库调整)

机械硬盘还没换 SSD?至少把 undo tablespacetemp tablespace 移到更快的盘上——它们是 IO 黑洞高发区。

为什么加了索引,IO 还没降下来?

索引不是万能解药。常见失效场景:

  • 查询条件用了函数:WHERE DATE(create_time) = '2026-01-01' → 索引失效,必须改成 create_time BETWEEN '2026-01-01' AND '2026-01-01 23:59:59'
  • 复合索引顺序错:INDEX(a,b) 支持 WHERE a=1 AND b=2,但不支持 WHERE b=2 单独查询
  • 返回字段太多:SELECT * 导致回表次数暴增,尤其当 text/blob 字段存在时,每次回表都可能触发额外 IO
  • 统计信息过期:ANALYZE TABLE 没跑过,优化器误判走索引比全表扫描还贵,干脆放弃

EXPLAIN FORMAT=JSONused_colum

nskey_length,比光看 key 字段更准。

真正卡住的地方,往往不在最显眼的慢查询里,而在那些每秒执行几百次、每次扫几万行、又没进慢日志的“温吞水”SQL——它们安静地把 IO 吞噬殆尽。