确认宽索引污染buffer pool:查performance_schema.table_io_waits_summary_by_table找count_read异常高的表,比对information_schema.statistics中是否建有5列以上二级索引;用explain format=json看key_parts与used_columns是否严重不匹配;检查innodb_buffer_pool_pages_misc是否持续>10%。

Buffer Pool命中率掉到700–850之间,且data_pages / total_pages接近1.0、youngs/s极低,大概率不是池子太小,而是宽索引把大量低频页塞进了Buffer Pool——删或裁剪比扩容更有效。
怎么确认是宽索引在污染Buffer Pool
宽索引(比如INDEX (a,b,c,d,e))本身不报错,但会让InnoDB读入整页索引数据,而实际查询只用前两列。这些“多余字段”占着内存却几乎不被访问,还拖累回表效率。
- 查
performance_schema.table_io_waits_summary_by_table,找COUNT_READ异常高的表,再比对information_schema.STATISTICS里是否建了5列以上的二级索引 - 用
EXPLAIN FORMAT=JSON跑慢查询,看used_columns和key_parts:如果key_parts显示用了5列,但used_columns只有2个,说明索引冗余 - 检查
Innodb_buffer_pool_pages_misc是否持续 > 10%,这可能是AHI因宽索引频繁分裂重建导致的副作用
删还是裁?优先裁剪,别直接DROP
直接删索引可能让原本走索引的查询退化成全表扫描。先评估真实使用率,再收缩宽度。
- 用
sys.schema_unused_indexes(MySQL 5.7+)筛出90天内SELECTS = 0的索引,这类可直接DROP INDEX - 对仍有查询但列数过多的索引,按
WHERE条件高频字段重排并截断:比如原索引(status, created_at, user_id, order_id),但95%查询只过滤前两个字段,就建新索引(status, created_at),再删旧索引 - 避免在
VARCHAR(255)字段上无前缀建索引——没加(name(10))的话,单页存不了几条记录,Buffer Pool利用率极差
覆盖索引能绕过回表污染,但得满足字段全包含
回表是宽索引拖累命中率的直接推手:先读二级索引页(可能命中),再根据主键去聚簇索引捞数据(大概率不命中)。覆盖索引把所有需要字段都塞进索引页,一步到位。
- 覆盖索引必须包含
WHERE、ORDER BY、GROUP BY涉及的所有列,否则仍会回表 - 不要为了覆盖而盲目加列:比如
SELECT id, name FROM t WHERE status=? ORDER BY created_at,只需(status, created_at, id, name),不必把user_id也塞进去 - 注意
TEXT/BLOB不能建在索引里,覆盖失败时会悄悄退化
真正难的是判断“哪些列该留、哪些该砍”——得结合EXPLAIN FORMAT=JSON里的used_columns和业务查询日志交叉验证,而不是只看SHOW CREATE TABLE。一个索引建了三年没人动过ORDER BY字段,却一直占着Buffer Pool空间,这种细节最容易被忽略。











