索引越多,insert/update/delete越慢,因每新增记录需同步更新所有相关索引的b+树,引发插入定位、页分裂、指针调整及磁盘刷盘等开销,高并发下易成瓶颈。

索引越多,INSERT/UPDATE/DELETE 越慢
每新增一条记录,MySQL 不只是写数据行,还要同步更新所有相关索引的 B+ 树结构。每个索引都是一份独立的、有序的数据副本,写入时都要做插入定位、页分裂、指针调整甚至磁盘刷盘。尤其在高并发写入场景下,索引维护开销会直接变成瓶颈。
常见错误现象:INSERT 延迟突然升高;SHOW PROCESSLIST 里大量线程卡在 update 或 insert 状态;innodb_row_lock_waits 指标明显上升。
- 每多一个二级索引,写操作至少多一次随机 I/O(尤其是非缓存命中时)
- 唯一索引还要额外校验重复值,比普通索引更重
-
TEXT/VARCHAR(255)类字段建索引,若实际值很长,索引页膨胀快,分裂更频繁 - 联合索引顺序不合理(比如把低区分度字段放前面),会导致索引树深度增加,写入路径变长
如何判断哪些索引是“冗余”或“无效”的
不是所有索引都在被用。很多是历史遗留、开发随手加、或只在某次慢查里临时加完就忘了删。真正有效的索引,应该有稳定且可衡量的查询收益,否则就是纯成本。
使用场景:线上表已运行数月,但没人定期看索引使用情况。
- 查
sys.schema_unused_indexes(MySQL 8.0+),它基于 performance_schema 统计,能直接列出长期未被任何查询使用的索引 - 用
SELECT * FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'db' AND TABLE_NAME = 't';对比INDEX_NAME和实际EXPLAIN中出现的索引,找“从不出现”的 - 注意联合索引的前缀覆盖:如果已有
(a,b,c),再建(a,b)就是冗余;但(b,a)不一定冗余,因为最左前缀匹配规则不同 - 唯一索引和主键冲突?
SHOW CREATE TABLE t看是否有多个UNIQUE KEY约束同一组字段
ALTER TABLE DROP INDEX 的实际影响和安全前提
删索引不是“立刻变快”,而是释放后续所有写入的开销。但操作本身会锁表(MySQL 5.6+ 的 ALGORITHM=INPLACE 可避免全表锁,仍需元数据锁等待),且不可逆——删错可能让某条关键查询从 10ms 慢成 10s。
参数差异:DROP INDEX idx_name ON t 在 MySQL 5.7 默认走 INPLACE,但若索引包含全文或空间类型,则强制 COPY;MySQL 8.0 默认更激进,但仍建议显式加 ALGORITHM=INPLACE, LOCK=NONE 并观察执行时间。
- 务必先在从库或影子库上跑一遍,确认没业务报错(比如应用层依赖
DUPLICATE KEY异常做逻辑分支) - 避开高峰期,
pt-online-schema-change可绕过锁表,但会延长操作时间,且对大表容易拖垮复制延迟 - 删完立刻查
information_schema.STATISTICS验证,别只信命令返回的Query OK - 监控删索引后
Innodb_buffer_pool_read_requests和Innodb_buffer_pool_reads是否同步下降——说明查询确实不再依赖该索引了
单列索引 vs 联合索引:选哪个更省写入成本
三个单列索引 (a)、(b)、(c) 和一个联合索引 (a,b,c),写入开销差得远。前者要维护三棵 B+ 树,后者只一棵,但查询能力不一定弱——关键看 WHERE 条件怎么组合。
性能影响:联合索引写入压力≈单个单列索引,但能覆盖更多查询模式;而三个单列索引会让每次写入触发三次独立的树维护流程。
- 优先按高频查询条件排序建联合索引,例如经常查
WHERE a=1 AND b>10,就建(a,b),别拆成两个单列 - 区分度高的字段放前面(如
user_id),低区分度的放后面(如status),能减少树的无效分支 - 不要为了“看起来全面”建
(a,b,c,d,e)—— 超过 4 列的联合索引,写入放大明显,且很少有查询真能用满全部列 - 注意
ORDER BY和GROUP BY字段是否能被同一联合索引覆盖,否则排序/分组会额外触发 filesort
索引成本藏在每次写入的毫秒级延迟里,积少成多才压垮系统。最危险的不是没索引,而是有一堆没人敢动、也没人记得为什么存在的索引。











