一聚教程网:一个值得你收藏的教程网站

热门教程

如何解决MySQL在处理高并发写入时因索引过多导致的性能抖动?

时间:2026-07-13 09:30:46 编辑:袖梨 来源:一聚教程网

索引过多是高并发写入抖动最常见根源——每多一个索引,写操作就得维护一棵B+树,导致写放大、锁等待变长、CPU刷脏页压力飙升;Index_length接近Data_length、Innodb_rows_inserted与Innodb_pages_written比值跌至2~5、EXPLAIN显示大量二级索引更新、LOG段中log i/o频率远超事务提交频率,均表明索引拖累写入;应优先删除仅服务于低频查询的单列WHERE索引(如created_at)、冗余索引及未被查询使用的索引。

索引过多是高并发写入抖动最常见、最容易被低估的根源——每多一个索引,INSERTUPDATEDELETE 就得多维护一棵 B+ 树,写放大直接翻倍。这不是“慢一点”,而是锁等待变长、CPU 刷脏页压力飙升、Innodb_buffer_pool_write_requestsInnodb_data_writes 比值严重失衡的信号。

怎么确认是索引拖慢了写入

别猜,用数据验证:

  • SHOW TABLE STATUS LIKE 'your_table',重点关注 Index_length:如果它接近甚至超过 Data_length,说明索引开销已和数据本身持平,非常危险
  • 对比指标:Innodb_rows_insertedInnodb_pages_written 的比值。正常应在 10~50 之间;若跌到 2~5,大概率是索引在疯狂刷盘
  • 执行 EXPLAIN FORMAT=JSON 看写操作是否触发了大量二级索引更新(used_columns 里出现非主键字段)
  • 观察 SHOW ENGINE INNODB STATUSG 中的 LOG 段:如果 log i/o's done 频率远高于事务提交频率,说明 redo 日志在为索引更新“擦屁股”

删哪些索引最安全、见效最快

优先砍掉对写入无贡献、只服务于低频查询的索引:

  • 单列 WHERE 索引,但该列 SELECT 查询占比 performance_schema.events_statements_summary_by_digest 查)
  • 前缀过长的 VARCHAR 索引,比如 INDEX (content(255)) —— 实际查询几乎不用到那么长
  • 重复索引:如已有 (user_id, status),又单独建了 (user_id),后者可删
  • 未被 EXPLAIN 任何查询命中的索引(用 sys.schema_unused_indexes 视图,MySQL 8.0+)
  • 写入频繁但查询从不带 ORDER BYGROUP BY 的字段索引(例如 updated_at 单独建索引)

改写索引结构降低写入成本

保留必要索引,但让它们更“轻”:

  • 把宽索引改成覆盖索引的最小集:比如原 INDEX (a,b,c,d,e) 只用于 WHERE a=? AND b=? ORDER BY c,可精简为 INDEX (a,b,c)
  • TINYINTENUM 替代 VARCHAR 做状态字段索引(如 status ENUM('pending','done','failed')),索引体积小、比较快
  • 避免在索引列上做函数转换:WHERE YEAR(created_at) = 2024 会让整个索引失效,改用 WHERE created_at >= '2024-01-01' AND created_at
  • 对高频写入表,考虑把部分查询索引移到 GENERATED COLUMN + STORED 上,再建索引——减少主表 DML 对索引的直接冲击

为什么删完索引后还是抖?盯住这几个隐藏点

索引删了,但抖动没消失,往往卡在这些细节:

  • innodb_flush_neighbors = 1 在 SSD 上没关:即使索引少了,这个参数仍会强制批量刷邻页,造成 I/O 放大。务必运行 SELECT @@innodb_flush_neighbors 确认是 0
  • 主键不是自增整数:用了 UUIDCHAR(36),导致页分裂持续发生,新插入总要调整 B+ 树结构 —— 这种抖动和索引数量无关,但表现相似
  • 表上有 FULLTEXT 索引:它不走普通 B+ 树,而是独立的倒排索引,每次写入都要同步更新,且无法用常规方式禁用
  • innodb_log_file_size 太小(如默认 48MB):索引删了,但日志切换太频繁,照样触发 checkpoint 抖动。建议设为 1G~2G

热门栏目