Skip to content

MySQL 冗余索引识别与清理

什么是冗余索引

冗余索引是指在数据库表中存在的、对于查询优化没有实际帮助,反而会影响插入、更新和删除操作性能的索引。冗余索引包括:

  • 重复索引:完全相同的索引定义,包括索引名、列名和顺序
  • 冗余覆盖索引:一个索引的列是另一个索引的前缀,或者两个索引包含相同的列但顺序不同
  • 无用索引:从未被查询使用过的索引

冗余索引的危害

  1. 存储空间浪费:每个索引都需要占用磁盘空间
  2. 写入性能下降:每次插入、更新或删除操作都需要维护所有相关索引
  3. 内存占用增加:索引会占用缓冲池空间,可能导致其他有用数据被置换
  4. 优化器负担加重:过多的索引会增加查询优化器的选择时间
  5. 维护成本增加:索引需要定期重建和优化

识别冗余索引的方法

1. 使用 INFORMATION_SCHEMA.STATISTICS

可以通过查询 INFORMATION_SCHEMA.STATISTICS 表来分析索引结构:

sql
SELECT 
    table_schema, 
    table_name, 
    index_name, 
    index_type, 
    GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ',') AS index_columns
FROM 
    INFORMATION_SCHEMA.STATISTICS
WHERE 
    table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
GROUP BY 
    table_schema, table_name, index_name, index_type
ORDER BY 
    table_schema, table_name, index_name;

2. 使用 sys.schema_redundant_indexes 视图

MySQL 5.7+ 提供了 sys.schema_redundant_indexes 视图,可以直接查看冗余索引:

sql
SELECT * FROM sys.schema_redundant_indexes;

3. 使用 pt-duplicate-key-checker 工具

Percona Toolkit 提供的 pt-duplicate-key-checker 工具可以自动识别冗余索引:

bash
# 安装 Percona Toolkit
sudo apt-get install percona-toolkit

# 使用 pt-duplicate-key-checker 检查冗余索引
pt-duplicate-key-checker --host=localhost --user=root --password=password

4. 使用 MySQL Enterprise Monitor

MySQL Enterprise Monitor 提供了索引分析功能,可以自动识别冗余索引。

5. 分析索引使用情况

可以通过 performance_schema 查看索引使用情况:

sql
-- 启用索引使用统计
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'wait/io/file/innodb/innodb_data_file';
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'statement/%';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%events_statements%';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%events_waits%';

-- 查看索引使用情况
SELECT 
    object_schema, 
    object_name, 
    index_name, 
    count_read, 
    count_write
FROM 
    performance_schema.table_io_waits_summary_by_index_usage
WHERE 
    object_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
ORDER BY 
    count_read + count_write ASC;

清理冗余索引的步骤

1. 备份数据库

在清理索引之前,一定要备份数据库,以防出现意外情况:

bash
# 使用 mysqldump 备份数据库
mysqldump -u root -p --all-databases > all_databases_backup.sql

2. 确认索引冗余性

对于识别出的冗余索引,需要仔细分析其是否真的冗余。特别是对于:

  • 复合索引的前缀索引
  • 不同顺序的相同列索引
  • 可能在不同查询场景下使用的索引

3. 测试删除索引的影响

在测试环境中删除索引,然后运行关键查询,确认性能没有下降:

sql
-- 在测试环境中删除索引
DROP INDEX index_name ON table_name;

-- 运行关键查询并查看执行计划
EXPLAIN SELECT * FROM table_name WHERE column1 = 'value1' AND column2 = 'value2';

-- 运行查询并记录执行时间
SELECT SQL_NO_CACHE * FROM table_name WHERE column1 = 'value1' AND column2 = 'value2';

4. 生产环境删除索引

在确认安全后,可以在生产环境中删除冗余索引:

sql
-- 删除冗余索引
DROP INDEX index_name ON table_name;

5. 监控删除后的性能

删除索引后,需要监控数据库性能,特别是写入操作的性能变化:

sql
-- 监控写入操作性能
SHOW GLOBAL STATUS LIKE 'Handler_write';
SHOW GLOBAL STATUS LIKE 'Handler_update';
SHOW GLOBAL STATUS LIKE 'Handler_delete';

-- 监控查询性能
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW GLOBAL STATUS LIKE 'Queries';

最佳实践

1. 定期检查冗余索引

建议定期(如每月或每季度)检查和清理冗余索引,特别是在以下情况下:

  • 表结构发生变化后
  • 应用程序查询模式发生变化后
  • 数据库性能下降时

2. 合理设计索引

  • 遵循索引设计原则:选择合适的索引列,避免过度索引
  • 优先考虑复合索引,而不是多个单列索引
  • 考虑索引的选择性和基数
  • 避免在低选择性列上创建索引

3. 使用工具辅助

  • 利用 sys.schema_redundant_indexes 视图进行日常检查
  • 使用 pt-duplicate-key-checker 工具进行深度分析
  • 考虑使用第三方监控工具进行持续监控

4. 分批处理

对于大型数据库,建议分批处理冗余索引,避免一次性删除过多索引导致系统负载过高。

5. 记录索引变更

建立索引变更的记录机制,包括:

  • 索引名称和表名
  • 删除原因
  • 删除时间
  • 操作人
  • 后续性能影响

常见问题(FAQ)

Q1: 如何区分重复索引和冗余覆盖索引?

A1: 重复索引是完全相同的索引定义,包括索引名、列名和顺序。冗余覆盖索引是指一个索引的列是另一个索引的前缀,或者两个索引包含相同的列但顺序不同,例如:

  • 索引1:(col1, col2)
  • 索引2:(col1) -- 这是索引1的前缀,是冗余索引
  • 索引3:(col2, col1) -- 包含相同列但顺序不同,可能是冗余索引

Q2: 如何处理从未使用过的索引?

A2: 对于从未使用过的索引,可以考虑删除,但需要注意:

  1. 确认监控周期足够长,覆盖了所有查询场景
  2. 检查是否有周期性任务或报表查询使用该索引
  3. 考虑业务发展,是否未来会使用该索引

Q3: 删除冗余索引会影响正在运行的查询吗?

A3: 删除索引是一个DDL操作,会锁定表(InnoDB在MySQL 5.6+支持在线DDL)。对于大型表,建议在业务低峰期执行,并使用在线DDL:

sql
ALTER TABLE table_name DROP INDEX index_name, ALGORITHM=INPLACE, LOCK=NONE;

Q4: 如何避免创建冗余索引?

A4: 可以通过以下方法避免创建冗余索引:

  1. 建立索引设计规范和评审流程
  2. 使用工具检查索引设计
  3. 定期审查现有索引
  4. 培训开发人员了解索引设计原则

Q5: 复合索引的顺序对冗余性有影响吗?

A5: 是的,复合索引的顺序很重要。例如,索引(col1, col2)和(col2, col1)是不同的索引,可能都有各自的使用场景,不一定是冗余索引。

Q6: 如何处理主键索引和唯一索引的关系?

A6: 主键索引是表的物理顺序,唯一索引是逻辑约束。主键索引通常是必须的,而唯一索引需要根据业务需求设计。如果唯一索引的列包含主键列,可能是冗余的。

Q7: 冗余索引会影响备份和恢复性能吗?

A7: 是的,冗余索引会增加备份和恢复的时间和空间消耗,因为备份工具需要处理更多的数据。

Q8: 如何监控索引使用情况?

A8: 可以通过以下方式监控索引使用情况:

  1. 使用 performance_schema.table_io_waits_summary_by_index_usage 视图
  2. 启用 slow_query_log 并分析查询执行计划
  3. 使用第三方监控工具如 Prometheus + Grafana
  4. 定期运行 pt-index-usage 工具分析慢查询日志

Q9: 如何处理外键索引?

A9: 外键索引是由MySQL自动创建的,用于维护引用完整性。在删除外键索引之前,需要先删除对应的外键约束。

Q10: 冗余索引清理后,需要重建其他索引吗?

A10: 一般情况下不需要,但如果其他索引存在碎片问题,可以考虑重建:

sql
-- 重建单个索引
ALTER TABLE table_name DROP INDEX index_name, ADD INDEX index_name (column1, column2);

-- 重建所有索引(会锁定表)
ALTER TABLE table_name ENGINE=InnoDB;

案例分析

案例1:电商订单表冗余索引

问题:电商订单表 orders 有以下索引:

  • PRIMARY KEY (order_id)
  • INDEX idx_user_id (user_id)
  • INDEX idx_user_id_order_date (user_id, order_date)
  • INDEX idx_order_date (order_date)

分析:idx_user_id 是 idx_user_id_order_date 的前缀,是冗余索引。

解决方案:删除 idx_user_id 索引。

结果

  • 减少了约10%的磁盘空间使用
  • 插入订单的性能提升了8%
  • 查询性能保持不变

案例2:用户表无用索引

问题:用户表 users 有一个 INDEX idx_email (email),但监控发现该索引从未被使用。

分析:应用程序可能使用了其他方式查询用户,或者该索引是为未来功能准备的。

解决方案

  1. 延长监控周期,确认该索引确实从未使用
  2. 检查应用程序代码,确认是否有使用该索引的查询
  3. 与开发团队沟通,确认该索引的用途
  4. 确认无用后,删除该索引

结果

  • 减少了磁盘空间使用
  • 提升了用户注册和更新的性能

工具推荐

工具名称功能适用场景
pt-duplicate-key-checker自动识别冗余索引定期检查和清理
sys.schema_redundant_indexes查看冗余索引日常监控
pt-index-usage分析索引使用情况慢查询分析
MySQL Enterprise Monitor自动监控和分析企业级监控
Percona Monitoring and Management (PMM)可视化监控和分析大规模数据库集群

不同版本的差异

MySQL 5.6

  • 支持在线DDL,但功能有限
  • 没有 sys.schema_redundant_indexes 视图
  • performance_schema 功能有限

MySQL 5.7

  • 增强了在线DDL支持
  • 引入了 sys.schema_redundant_indexes 视图
  • 增强了 performance_schema 功能

MySQL 8.0

  • 进一步增强了在线DDL支持
  • 引入了不可见索引,便于测试索引删除
  • 增强了 sys schema 视图
  • 引入了即时DDL,某些DDL操作更快