索引越多INSERT越慢,因每增一个二级索引就需同步维护一棵B+树、生成redo日志、加剧刷盘与随机写;Index_length接近Data_length或Innodb_pages_written/Innodb_rows_inserted比值跌至2~5即为典型征兆。

索引越多,INSERT越慢——这不是线性下降,而是指数级恶化。每多一个二级索引,每次写入就要多维护一棵 B+ 树、多一次刷脏页、多一次 redo log 记录,还可能触发锁等待和 CPU 刷盘风暴。
为什么每个索引都会让INSERT变慢?
MySQL 的 INSERT 不只是往主键 B+ 树里塞一行数据。它还要同步更新所有相关二级索引:
- 每棵二级索引都是一棵独立的 B+ 树,插入时需定位叶节点、分裂页(若满)、写入新记录
- 每个索引变更都要生成 redo log,并在事务提交时刷盘(取决于
innodb_flush_log_at_trx_commit) - 索引数据块要加载进
innodb_buffer_pool,频繁写入会挤占缓存空间,加剧物理 I/O - 多个索引更新可能跨不同数据页,导致随机写放大,SSD 也扛不住持续高随机写
怎么快速判断是不是索引拖垮了INSERT?
别靠猜,看这几个硬指标:
-
SHOW TABLE STATUS LIKE 'your_table':如果Index_length接近甚至超过Data_length,说明索引体积已和数据本身持平,危险信号 - 查全局状态:
SHOW GLOBAL STATUS LIKE 'Innodb_rows_inserted'和Innodb_pages_written;比值跌到 2~5(正常应为 10~50),基本可断定索引在疯狂刷盘 -
EXPLAIN FORMAT=JSON执行一个带WHERE的UPDATE或DELETE,看used_columns是否列出大量非主键字段——说明二级索引被高频更新 -
SHOW ENGINE INNODB STATUS\G中 LOG 段:如果log i/o's done频率远高于事务提交次数,说明 redo 日志正为索引“擦屁股”
哪些索引最该删,删了见效最快?
优先砍掉对写入零贡献、只服务低频查询的索引:
- 单列
WHERE索引,但该列在performance_schema.events_statements_summary_by_digest中查询占比极低(比如created_at单独建索引,但实际查询几乎都带其他条件) - 前缀过长的索引,如
INDEX (content(255))——99% 查询只用前 20 字符,却为整行维护 255 字节索引 - 冗余索引:已有
(user_id, status),又单独建了(user_id),后者可直接删 - 未被任何查询命中的索引:
sys.schema_unused_indexes(MySQL 8.0+)能直接列出 - 仅用于
ORDER BY updated_at但该字段从不参与WHERE或JOIN的单列索引——排序走文件排序(Using filesort)比维护索引便宜得多
删索引前最容易踩的坑
操作本身简单,但忽略上下文就容易翻车:
-
ALTER TABLE t DROP INDEX idx_name会锁表(尤其大表),线上执行务必避开高峰;DISABLE KEYS对 InnoDB 仅部分支持,且对唯一索引无效 - 别在有
UNIQUE或PRIMARY KEY约束的列上临时禁用索引,否则重复数据导入会静默失败或报错 - 删完别忘了
ANALYZE TABLE,否则优化器统计信息滞后,可能选错执行计划 - 监控要跟上:删索引后观察
Innodb_buffer_pool_write_requests / Innodb_data_writes比值是否回升,确认写入压力真降下来了
真正难的不是删哪个索引,而是识别出“这个索引看起来有用,但其实业务早已不用”。很多表的索引是三年前为某次活动加的,活动下线后没人清理,却一直拖着写入性能往下掉。



















