清理无用索引能显著降低MySQL写操作开销,需通过Java可观测性识别真实无用索引,由DBA安全删除并闭环验证,兼顾读写性能平衡。

清理无用索引能显著降低 MySQL 写操作(INSERT/UPDATE/DELETE)的维护开销,因为每写一行数据,MySQL 都要同步更新所有相关索引的 B+ 树结构。Java 应用本身不直接删索引,但作为业务系统,它决定了哪些索引被实际使用、哪些长期闲置。关键在于:用 Java 侧可观测性辅助识别,靠 DBA 或运维侧安全执行删除,整个过程需闭环验证。
索引维护开销从哪来
每次写入时,MySQL 不仅要写聚簇索引(主键),还要维护所有二级索引:
- 插入新行 → 每个二级索引都要插入一条索引记录
- 更新索引列 → 原索引项删除 + 新索引项插入(相当于两次 I/O)
- 删除行 → 所有二级索引中对应条目都要标记为删除(后续 purge)
索引越多、越宽(字段多、类型大)、越冷(长期不用但还挂着),写放大就越严重。实测中,10 个冗余索引可使大表批量导入速度下降 30%~60%。
用 Java 应用侧辅助识别“真无用”索引
别只信 sys.schema_unused_indexes —— 它可能漏掉低频但关键的索引(如凌晨对账任务)。Java 服务可提供更真实的使用线索:
- 开启慢查询日志并接入 ELK 或 SkyWalking,筛选出高频执行但未走索引的 SQL,反向定位“本该用却没用上”的索引(说明设计不合理,不是无用,而是失效)
- 在 MyBatis 或 JPA 的 SQL 日志中 grep
USE INDEX、FORCE INDEX,确认是否有硬编码索引名——这类索引绝不能删 - 对接应用监控(如 Micrometer + Prometheus),统计核心接口的平均响应时间与 P95,在疑似索引删除前后做对比,观察写链路是否明显变快
安全删除前必须交叉验证的三类依赖
Java 服务常隐式依赖某些索引,删错会导致接口雪崩:
立即学习“Java免费学习笔记(深入)”;
- 查
information_schema.KEY_COLUMN_USAGE,确认该索引是否是外键约束自动生成的(CONSTRAINT_NAME非空) - 运行
SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage WHERE COUNT_STAR = 0 AND INDEX_NAME = 'xxx',但需确保 performance_schema 已开启且采集超 48 小时(避免刚上线就删) - 检查定时任务模块(如 XXL-JOB、Quartz)的 SQL,特别是报表、导出、对账类逻辑——它们往往只在凌晨跑一次,日志里不显眼却强依赖特定索引
生产环境删索引的 Java 友好节奏
避免 ALTER TABLE DROP INDEX 直接锁表影响 Java 接口:
- 优先在从库执行删除,观察 1~2 天复制延迟和慢查询有无突增;确认无异常后,再在主库低峰期(如 2:00–4:00)操作
- 大表(千万级以上)务必用在线 DDL:
ALTER TABLE t DROP INDEX idx_name, ALGORITHM=INPLACE, LOCK=NONE(MySQL 5.6+ 支持) - 单次只删 1 个索引,两次操作间隔 ≥30 分钟;配合 Java 侧的 Sentinel 降级开关或配置中心灰度,发现异常可快速回滚(回滚即重建索引)
删完之后别忘了收尾动作
- 执行
ANALYZE TABLE t更新统计信息,防止优化器因过时的 cardinality 误选执行计划 - 检查
information_schema.INNODB_METRICS中index_lock_wait、dml_rows_inserted等指标,确认写入吞吐是否提升 - 在 Java 应用的数据库连接池(如 HikariCP)监控页,观察
active和idle连接数波动,间接判断锁竞争是否缓解
不复杂但容易忽略的是:很多“无用索引”其实承担着覆盖扫描(Covering Index)功能,比如 SELECT name, status FROM user WHERE id = ? 走 INDEX(id, name, status) 就无需回表——删了它,看似写入快了,但读性能可能断崖下跌。所以,清理目标不是“少”,而是“准”。


















