insert变慢八成因索引过多——每增一个二级索引,insert需多执行一次b+树插入、页分裂维护及undo/redo日志写入;5个以上时单行开销常翻2–3倍。

INSERT 变慢,八成是因为索引太多
加索引不是免费的——每多一个二级索引,INSERT 就得多做一次 B+ 树插入、多维护一份页分裂逻辑、多写一次 undo log 和 redo log。当表有 5 个以上二级索引时,单行写入开销常翻 2–3 倍。这不是理论值,SHOW PROCESSLIST 里卡在 updating 状态、慢查询日志中 INSERT 进 top 10、监控里 Innodb_row_lock_waits 突增,都是典型信号。
为什么优化器也怕索引多
优化器选执行计划前,要评估每个可用索引的成本。索引越多,它要计算的路径就越多;尤其当存在多个可覆盖相同 WHERE 条件的索引(比如 idx_a、idx_a_b、idx_a_b_c),优化器可能反复比较、误判统计信息偏差,最终选错索引或干脆退化为全表扫描。更隐蔽的问题是:ANALYZE TABLE 更新统计信息的频率跟不上数据变化速度,索引基数(Cardinality)长期不准,导致优化器“瞎猜”。
怎么判断哪些索引该删
别靠经验猜,用系统视图直接看真实使用情况:
-
SELECT * FROM sys.schema_unused_indexes—— MySQL 8.0+ 自带,列出近 30 天完全没被任何查询命中的索引 -
SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage WHERE count_star = 0—— 更底层,连 DML 触发的索引更新都算进去 - 手动检查
EXPLAIN FORMAT=JSON输出里的used_columns字段,确认实际生效的索引列是否和你建的一致
注意:WHERE a = ? AND b > ? ORDER BY c 这类查询,idx_a_b_c 能覆盖全部,idx_a 和 idx_b 就是冗余的。
删索引前必须做的三件事
直接 DROP INDEX 风险高,尤其在线上大表:
- 先查锁影响:
SELECT * FROM information_schema.INNODB_TRX WHERE trx_query LIKE '%DROP INDEX%',避免长事务阻塞 - 确认 binlog 格式是
ROW,否则 DDL 可能导致主从不一致 - 对核心表,用
pt-online-schema-change --alter "DROP INDEX idx_name" D=db,t=tbl替代原生 DDL,避免锁表
真正难的不是删,而是删完之后——业务方突然发现某个报表接口变慢了。所以删之前,一定用 slow_query_log + min_examined_row_limit = 1000 拉一周真实 SQL 流量,重点盯那些刚被删掉索引字段参与查询的语句。











