<p>SELECT 会引发磁盘IO暴增,因其导致InnoDB频繁回表随机读,尤其在无索引或索引覆盖不全时;应避免SELECT 、使用覆盖索引、优化ORDER BY和COUNT(*)查询。</p>

为什么 SELECT * 会让磁盘 IO 暴增
不是所有查询慢都怪 CPU 或网络,很多是 MySQL 在硬盘上反复跳着找数据——也就是随机读。尤其是没走索引、或者索引覆盖不全时,InnoDB 得回表查 PRIMARY KEY 对应的完整行,每行可能落在不同页、不同物理位置,机械盘上一次随机读耗时可能是顺序读的 10–100 倍。
- 避免
SELECT *,只查真正需要的字段;能用覆盖索引就别回表 - 如果业务必须查多列,考虑把高频组合字段建联合索引(比如
INDEX (user_id, status, created_at)),让查询完全在索引中完成 -
EXPLAIN看执行计划里type是不是ALL或index,Extra里有没有Using filesort或Using temporary——这些往往意味着大量磁盘扫描
innodb_read_io_threads 和 innodb_write_io_threads 怎么调
这两个参数控制后台线程数,影响并发读写能力,但调太高反而引发锁竞争或上下文切换开销。默认值(Linux 下各 4)对多数 SSD 场景够用,机械盘才值得调高。
- SSD:保持默认或最多设为
8;超过后iostat -x会看到%util没涨,但await反而升高 - 机械盘:可试设为
8–12,但必须配合innodb_io_capacity(建议设为磁盘随机 IOPS 的 70%,比如 150 IOPS 就设100) - 改完要重启 MySQL;线上调参前先在从库验证,观察
SHOW ENGINE INNODB STATUS里的FILE I/O部分是否有线程长期空闲或积压
ORDER BY 不走索引导致磁盘排序(Using filesort)
当 ORDER BY 字段不在索引最左前缀,或混合了 ASC/DESC,MySQL 就无法利用索引有序性,被迫把结果集捞出来再排序——数据量大时直接写临时文件到磁盘,IO 瞬间拉满。
- 确保
ORDER BY字段是索引的连续最左前缀,例如INDEX (a, b, c)支持ORDER BY a, b,但不支持ORDER BY b, c - 避免
ORDER BY RAND();真要随机抽样,用WHERE id >= FLOOR(RAND() * (SELECT MAX(id) FROM t)) LIMIT 1替代 - 如果排序字段有大量重复值(比如
status只有 3 个值),加LIMIT也救不了——因为优化器仍可能选错执行路径;此时加FORCE INDEX强制走覆盖索引更稳
大表 COUNT(*) 为什么卡住 IO 队列
COUNT(*) 在 InnoDB 里本质是遍历聚集索引,一行行加 1。没 WHERE 条件时,它得扫完整个主键 B+ 树叶子节点——数据量千万级以上,就是几 GB 的顺序读,IO 队列直接堵死,还可能拖慢其他查询。
- 别用
COUNT(*)做实时总数展示;用缓存(Redis)或定时任务写入汇总表 - 如果必须近似值,查
information_schema.TABLES里的TABLE_ROWS(注意:该值不准,仅 MyISAM 实时,InnoDB 是估算) - 想精确又不想扫表?给常用条件建汇总表,比如每日统计
INSERT INTO daily_count SELECT DATE(created_at), COUNT(*) FROM orders GROUP BY DATE(created_at)
磁盘 IO 优化最易被忽略的点:不是参数调得越激进越好,而是让数据“尽量别动”。索引设计是否覆盖、查询是否回表、排序是否落地——这些比调 innodb_io_capacity 影响大得多。










