索引多会让insert/update变慢,因为每写入一行,innodb需同步维护所有二级索引的b+树结构,导致n倍的磁盘随机写、redo log记录、buffer pool加载及锁竞争,唯一索引还额外触发重复值校验,开销非线性叠加。

为什么索引多会让INSERT/UPDATE变慢
每写入一行,InnoDB 不仅要更新聚簇索引(主键 B+ 树),还要同步维护所有二级索引的 B+ 树结构;每个索引都意味着一次额外的磁盘随机写、一次 redo log 记录、一次 buffer pool 页面加载。索引从 2 个增加到 8 个,写入耗时可能翻 3 倍以上——这不是线性增长,是指数级叠加开销。
- 写操作需维护 N 个索引 → N 倍 B+ 树分裂、页合并、锁竞争
- 唯一索引还会触发重复值校验(额外查索引),进一步拖慢速度
- 大量索引挤占
innodb_buffer_pool_size,导致数据页缓存命中率下降,连带影响读性能 - DDL 操作(如
ALTER TABLE ADD INDEX)期间表锁或元数据锁时间显著拉长
如何快速识别哪些索引该删
别凭感觉删,先看真实使用情况。MySQL 5.6+ 的 performance_schema.table_io_waits_summary_by_index_usage 能告诉你每个索引被查询用过几次。但更常用、兼容性更好的方式是结合慢日志 + information_schema.STATISTICS + 查询频次人工比对:
- 查出所有索引:
SHOW INDEX FROM orders - 过滤长期未出现在
WHERE/JOIN/ORDER BY中的索引(比如只在冷门导出脚本里出现过) - 删除低选择性字段的单列索引:
SELECT COUNT(DISTINCT status)/COUNT(*) FROM orders若结果 status 单独建索引大概率无效 - 已有
idx_user_id_created_at,就别再单独建idx_user_id—— 它是冗余的 - 云数据库(如阿里云 RDS)可直接用控制台「索引分析」功能,标出“未使用索引”和“相似索引”
批量导入时临时禁用索引的实操边界
ALTER TABLE t DISABLE KEYS 对 MyISAM 有效,但 InnoDB 从 5.7 开始仅在 LOAD DATA INFILE 场景下才真正跳过二级索引构建;普通 INSERT 语句仍会实时更新索引。所以生产中更稳妥的组合是:
- 导入前:
SET autocommit = 0; SET unique_checks = 0; SET foreign_key_checks = 0; - 用
INSERT INTO t VALUES (),(),()...;批量插入(单条最多 1000 行,避免超max_allowed_packet) - 导入后:
SET unique_checks = 1; SET foreign_key_checks = 1;并立即执行ANALYZE TABLE t; - 若必须删重建索引,优先用
DROP INDEX idx_name ON t;+ 导入 +CREATE INDEX,而不是依赖DISABLE KEYS - 注意:
unique_checks = 0期间插入重复唯一键不会报错,只会静默丢弃后一条,务必确认业务允许
复合索引设计如何减少索引总数
一个设计得当的联合索引,能替代 2–3 个单列索引。关键不是字段越多越好,而是按高频查询模式排列顺序:
- 等值查询字段放最左(如
user_id = ?),范围/排序字段放右(如created_at > ? ORDER BY created_at DESC) - 区分度高的字段优先靠左:
user_id(百万级唯一值)比status(3–5 个枚举)更适合放前面 - 覆盖索引能省回表:如果常查
SELECT order_id, user_id, status FROM orders WHERE user_id = ?,建idx_user_id_status_order_id就够了,不用额外索引 - 避免过度设计:不要为“未来可能有”的查询提前建索引,等
EXPLAIN真实暴露type=ALL再加 - 单表索引数超过 5 个,就要强制 Review —— 这不是硬指标,而是提醒你:是不是把本该拆分的查询逻辑,全压在一张表上了?
phone(varchar)字段加了索引,但应用层传参却是数字类型 WHERE phone = 13800001111,索引完全失效,却还在默默拖慢写入。所以删索引之前,先确认它到底有没有被用上。











