为什么 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区块里。
