select * 会因读取整行(含大字段)、回表及无覆盖索引导致io突增;缓冲池过小引发频繁刷盘;order by无索引或索引失效触发文件排序;批量写入批次过大反而加重io。

为什么 SELECT * 会让 IO 突增
mysql 在执行 SELECT * 时,会把整行所有字段(包括大字段如 TEXT、BLOB)从磁盘读入内存,哪怕你只用其中一两个字段。尤其当表有宽列、行数多、且没有覆盖索引时,InnoDB 必须回表读聚簇索引,触发大量随机 IO。
实操建议:
- 显式列出需要的字段,比如改
SELECT *为SELECT id, name, status - 对高频查询字段建立覆盖索引,让
SELECT能在二级索引中完成全部读取,避免回表 - 检查
SHOW EXPLAIN输出,确认Extra列是否含using index(走覆盖索引)或Using where; Using index condition
innodb_buffer_pool_size 设太小会导致反复刷盘
InnoDB 缓冲池是 MySQL 最关键的内存区域,它缓存数据页和索引页。若 innodb_buffer_pool_size 小于活跃数据集大小,就会频繁淘汰旧页、加载新页,引发大量磁盘 IO —— 表现为 innodb_data_reads 和 innodb_data_read 持续升高。
实操建议:
- 生产环境建议设为物理内存的 50%–75%,但不低于 1GB;可用
SELECT (SELECT count(*) FROM information_schema.INNODB_BUFFER_POOL_PAGES_DATA) * 16384 / 1024 / 1024 AS buffer_used_mb;估算当前实际使用量 - 启用
innodb_buffer_pool_instances(如设为 8),减少并发访问缓冲池时的锁争用 - 避免在低配机器上硬塞大 buffer pool,否则可能触发系统 OOM Killer 杀掉 mysqld 进程
ORDER BY + LIMIT 不走索引时 IO 爆炸的典型场景
比如 SELECT * FROM orders ORDER BY created_at DESC LIMIT 20,若 created_at 没有索引,MySQL 会全表扫描排序,把全部数据读入临时表(可能落磁盘),IO 和 CPU 双高。
更隐蔽的是:即使 created_at 有单列索引,但查询带了 WHERE user_id = ?,而没建联合索引,也可能导致索引失效、退化为文件排序(Using filesort)。
实操建议:
- 对
ORDER BY字段单独建索引,或与WHERE条件字段组合建联合索引,顺序按「等值条件 → 最左前缀 → 排序字段」排列,例如(user_id, created_at) - 用
EXPLAIN确认key列是否命中索引,Extra是否出现Using index或Using index condition - 避免在大表上用
OFFSET分页,改用基于游标的分页(如WHERE id > ? ORDER BY id LIMIT 20)
批量写入时关掉 autocommit 反而加重 IO?
很多人认为“手动事务 + 多条 INSERT”能减少 IO,但若每批提交过大(如 10 万行),或未调优日志相关参数,反而会拖慢:redo log 刷盘压力大、buffer pool 脏页堆积、甚至触发 checkpoint 阻塞。
实操建议:
- 保持
autocommit=1用于小事务;对大批量导入,用START TRANSACTION; ... COMMIT;,但单批次控制在 1k–10k 行(视行大小调整) - 增大
innodb_log_file_size(如 512MB)和innodb_log_buffer_size(如 64MB),降低 redo log 刷盘频率 - 导入前临时关闭唯一性检查(
SET unique_checks=0)、外键检查(SET foreign_key_checks=0),导入后再打开
IO 优化不是堆参数或加索引就能解决的,核心在于理解数据访问模式——哪些页被反复读?哪些写入触发了刷盘链路?真正卡点往往藏在 EXPLAIN 的 Extra 字段、SHOW ENGINE INNODB STATUS 的 LOG 和 BUFFER POOL AND MEMORY 区块里。