Skip to content

MySQL 索引维护

索引维护概述

索引维护的重要性

索引在MySQL数据库中扮演着加速查询的重要角色,但索引并非一劳永逸,需要定期维护。随着数据的不断变化,索引可能会出现以下问题:

  • 碎片化:频繁的插入、更新、删除操作导致索引页分裂,产生碎片,降低查询效率
  • 过时索引:业务需求变化,原有的索引不再被查询使用,浪费存储空间和维护成本
  • 冗余索引:存在多个功能相似的索引,增加写入操作的开销
  • 统计信息不准确:索引统计信息过时,导致查询优化器选择错误的执行计划
  • 索引膨胀:索引大小不合理增长,影响整体性能

索引维护的目标

目标具体描述
提高查询性能确保索引被有效使用,减少查询时间
降低维护成本减少不必要的索引,降低写入操作的开销
优化存储减少索引占用的存储空间,提高存储效率
提高写入性能减少索引对插入、更新、删除操作的影响
确保统计信息准确帮助查询优化器选择正确的执行计划

索引维护的频率

索引维护的频率取决于以下因素:

因素维护频率建议
数据更新频率频繁更新:每周维护
稳定更新:每月维护
很少更新:每季度维护
数据量大小大数据量:更频繁维护,如每周或每两周
业务重要性核心业务:更频繁维护,确保高性能
查询性能变化性能下降时立即维护,及时解决问题

索引维护前的准备

工具准备

工具用途安装命令
MySQL客户端执行索引维护命令系统自带
pt-index-usage分析索引使用情况percona-toolkit
pt-duplicate-key-checker检测冗余索引percona-toolkit
pt-table-sync同步数据(维护索引后可能需要)percona-toolkit
mysqlcheck检查和修复表系统自带

环境准备

  1. 备份数据

    bash
    # 全量备份
    mysqldump -u root -p --all-databases > full_backup.sql
    
    # 或备份特定表
    mysqldump -u root -p mydb mytable > table_backup.sql
  2. 选择维护时间

    • 业务低峰期(如凌晨2:00-4:00)
    • 提前通知相关团队,做好沟通
    • 预留足够的维护时间,考虑回滚时间
  3. 监控准备

    • 开启慢查询日志,便于分析查询性能
    • 监控数据库性能指标,如CPU、内存、I/O等
    • 准备回滚方案,确保出现问题时可以快速恢复

索引使用情况分析

查看索引使用统计

使用Performance Schema

sql
-- 确保Performance Schema启用
SHOW VARIABLES LIKE 'performance_schema';

-- 查看索引使用情况
SELECT 
    OBJECT_SCHEMA AS db_name,
    OBJECT_NAME AS table_name,
    INDEX_NAME,
    COUNT_FETCH AS index_scans,
    COUNT_INSERT AS insert_ops,
    COUNT_UPDATE AS update_ops,
    COUNT_DELETE AS delete_ops
FROM 
    performance_schema.table_io_waits_summary_by_index_usage
WHERE 
    OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema')
ORDER BY 
    index_scans ASC;

分析未使用的索引

sql
-- 查找未使用的索引
SELECT 
    t.TABLE_SCHEMA,
    t.TABLE_NAME,
    t.INDEX_NAME
FROM 
    information_schema.STATISTICS t
LEFT JOIN 
    performance_schema.table_io_waits_summary_by_index_usage s ON 
        t.TABLE_SCHEMA = s.OBJECT_SCHEMA AND 
        t.TABLE_NAME = s.OBJECT_NAME AND 
        t.INDEX_NAME = s.INDEX_NAME
WHERE 
    s.INDEX_NAME IS NULL AND 
    t.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema') AND
    t.INDEX_NAME != 'PRIMARY';

使用Percona工具分析

使用pt-index-usage

bash
# 分析慢查询日志中的索引使用情况
pt-index-usage slow.log --database=mydb > index-usage.txt

# 查看报告摘要
cat index-usage.txt | grep -A 20 "SUMMARY"

# 查看未使用的索引
cat index-usage.txt | grep "^# Indexes with no hits"

检测冗余索引

bash
# 检测冗余索引
pt-duplicate-key-checker h=localhost,u=root,p=password --database=mydb

# 输出示例
# # 圝圝圝 Table mydb.users圝圝圝
# # 圝圝圝 mydb.users idx_username (username) is a left-prefix of mydb.users idx_username_email (username, email)
# # 圝圝圝 mydb.users idx_username is redundant
# # Key definitions:
# #   KEY `idx_username` (`username`),
# #   KEY `idx_username_email` (`username`,`email`),
# # Column types:
# #   `username` varchar(50) not null
# #   `email` varchar(100) not null
# # To remove redundant index:
# ALTER TABLE `mydb`.`users` DROP INDEX `idx_username`;

索引维护操作

重建索引

重建索引是解决索引碎片、更新统计信息的有效方法,适用于索引碎片化严重或统计信息不准确的情况。

使用ALTER TABLE重建

sql
-- 重建表的所有索引(会锁表,建议在低峰期使用)
ALTER TABLE users ENGINE=InnoDB;

-- 或
ALTER TABLE users FORCE;

-- 重建单个索引
ALTER TABLE users DROP INDEX idx_username, ADD INDEX idx_username (username);

使用OPTIMIZE TABLE

sql
-- 优化表,重建索引(会锁表)
OPTIMIZE TABLE users;

-- 对于InnoDB表,OPTIMIZE TABLE会重建表和索引
-- 等同于 ALTER TABLE users ENGINE=InnoDB

使用REPAIR TABLE

sql
-- 仅适用于MyISAM表,InnoDB表不支持
REPAIR TABLE myisam_table QUICK;
REPAIR TABLE myisam_table EXTENDED;

在线重建索引(MySQL 8.0+)

MySQL 8.0引入了在线重建索引的功能,可以在不锁表或只使用共享锁的情况下重建索引,极大地减少了对业务的影响:

sql
-- 在线重建索引,无锁
ALTER TABLE users DROP INDEX idx_username, ADD INDEX idx_username (username) ALGORITHM=INPLACE, LOCK=NONE;
ALGORITHM选项描述
COPY复制表到新表,锁表时间长,适用于所有版本
INPLACE原地重建,锁表程度低,MySQL 5.6+支持
DEFAULT自动选择最佳算法,推荐使用
LOCK选项描述
SHARED共享锁,允许读,阻止写,MySQL 5.6+支持
EXCLUSIVE排他锁,阻止读写,默认选项
NONE无锁,允许读写,MySQL 8.0+支持
DEFAULT自动选择最佳锁策略,推荐使用

删除未使用和冗余索引

删除未使用和冗余索引可以减少维护成本,提高写入性能。

删除未使用的索引

sql
-- 生成删除未使用索引的语句
SELECT 
    CONCAT('ALTER TABLE ', TABLE_SCHEMA, '.', TABLE_NAME, ' DROP INDEX ', INDEX_NAME, ';') AS drop_sql
FROM 
    information_schema.STATISTICS t
LEFT JOIN 
    performance_schema.table_io_waits_summary_by_index_usage s ON 
        t.TABLE_SCHEMA = s.OBJECT_SCHEMA AND 
        t.TABLE_NAME = s.OBJECT_NAME AND 
        t.INDEX_NAME = s.INDEX_NAME
WHERE 
    s.INDEX_NAME IS NULL AND 
    t.TABLE_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema') AND
    t.INDEX_NAME != 'PRIMARY';

-- 执行生成的删除语句,建议先在测试环境验证
ALTER TABLE mydb.users DROP INDEX idx_unused;

删除冗余索引

sql
-- 使用pt-duplicate-key-checker生成的删除语句
ALTER TABLE `mydb`.`users` DROP INDEX `idx_username`;

更新索引统计信息

MySQL的查询优化器依赖于准确的索引统计信息来选择最佳执行计划,定期更新统计信息非常重要。

自动更新统计信息

InnoDB会自动更新统计信息,但在某些情况下(如大量数据变更)需要手动更新:

sql
-- 查看自动更新统计信息的配置
SHOW VARIABLES LIKE 'innodb_stats_auto_recalc';
SHOW VARIABLES LIKE 'innodb_stats_persistent_sample_pages';

手动更新统计信息

sql
-- 更新单个表的统计信息
ANALYZE TABLE users;

-- 更新多个表的统计信息
ANALYZE TABLE users, orders, products;

-- 使用InnoDB特定的统计信息更新(MySQL 5.6+)
ANALYZE TABLE users PERSISTENT FOR ALL;

大表统计信息更新优化

对于大表,更新统计信息可能会很慢,可以调整采样页数,减少扫描的数据量:

sql
-- 临时调整采样页数,适用于当前会话
SET SESSION innodb_stats_persistent_sample_pages = 1000;
ANALYZE TABLE large_table;

-- 或永久调整表的采样页数
ALTER TABLE large_table STATS_SAMPLE_PAGES = 1000;
ANALYZE TABLE large_table;

索引维护的最佳实践

日常维护

  1. 定期分析索引使用情况

    • 每周使用pt-index-usage分析慢查询日志,了解索引使用情况
    • 每月使用pt-duplicate-key-checker检测冗余索引,及时清理
    • 每季度全面检查索引使用情况,评估索引效果
  2. 监控索引相关指标

    • 监控慢查询数量,及时发现性能问题
    • 监控全表扫描的查询,评估是否需要添加索引
    • 监控索引的大小变化,防止索引膨胀
  3. 制定索引维护计划

    • 明确维护周期,根据业务需求调整
    • 明确维护流程,确保操作规范
    • 明确责任人,落实维护工作
    • 准备回滚方案,降低风险

维护中的注意事项

  1. 避免在业务高峰期维护

    • 选择凌晨或业务低峰期进行维护
    • 提前通知相关团队,做好沟通
    • 预留足够的维护时间,考虑回滚时间
  2. 备份数据

    • 维护前备份相关表或数据库
    • 确保备份可用,定期验证备份有效性
    • 制定回滚方案,确保出现问题时可以快速恢复
  3. 逐步执行

    • 先在测试环境验证维护操作,确保安全
    • 生产环境逐步执行,每执行一步验证结果
    • 从非核心表开始维护,积累经验后再维护核心表
  4. 监控执行过程

    • 监控数据库负载,防止资源耗尽
    • 监控查询性能,确保维护效果
    • 监控锁等待情况,避免长时间锁表影响业务
  5. 记录维护过程

    • 记录执行的命令和参数
    • 记录执行时间和持续时间
    • 记录执行结果和性能变化
    • 记录遇到的问题和解决方案

维护后的验证

  1. 验证索引状态

    sql
    -- 查看索引是否存在
    SHOW INDEX FROM users;
    
    -- 查看索引使用情况
    SHOW GLOBAL STATUS LIKE 'Handler_read%';
  2. 验证查询性能

    sql
    -- 使用EXPLAIN验证查询计划
    EXPLAIN SELECT * FROM users WHERE username = 'admin';
    
    -- 对比维护前后的查询执行时间
    SELECT BENCHMARK(100000, SELECT * FROM users WHERE username = 'admin');
  3. 验证应用功能

    • 执行核心业务查询,确保功能正常
    • 验证写入操作正常,确保数据完整性
    • 验证应用功能正常,确保业务不受影响

索引维护案例

案例一:电商订单表索引维护

场景描述

某电商平台的订单表(orders)有1000万条数据,近期查询性能下降,慢查询数量增加,需要进行索引维护。

维护步骤

  1. 分析索引使用情况

    bash
    pt-index-usage slow.log --database=orders_db > index-usage.txt
    pt-duplicate-key-checker h=localhost,u=root,p=password --database=orders_db
  2. 发现问题

    • 发现3个未使用的索引,占用存储空间
    • 发现2个冗余索引,增加写入开销
    • 索引存在碎片化,查询效率下降
  3. 制定维护计划

    • 时间:凌晨2:00-4:00,业务低峰期
    • 步骤:先删除冗余索引,再删除未使用的索引,最后重建核心索引
    • 回滚方案:准备好表备份,随时可以恢复
  4. 执行维护

    sql
    -- 删除冗余索引
    ALTER TABLE orders DROP INDEX idx_user_id, DROP INDEX idx_order_date;
    
    -- 删除未使用的索引
    ALTER TABLE orders DROP INDEX idx_unused1, DROP INDEX idx_unused2, DROP INDEX idx_unused3;
    
    -- 重建核心索引,使用在线重建减少影响
    ALTER TABLE orders DROP INDEX idx_order_no, ADD INDEX idx_order_no (order_no) ALGORITHM=INPLACE, LOCK=NONE;
    ALTER TABLE orders DROP INDEX idx_user_id_order_date, ADD INDEX idx_user_id_order_date (user_id, order_date DESC) ALGORITHM=INPLACE, LOCK=NONE;
    
    -- 更新统计信息
    ANALYZE TABLE orders;
  5. 验证结果

    • 查询性能提升30%,平均响应时间从500ms降至350ms
    • 写入性能提升15%,平均响应时间从200ms降至170ms
    • 索引总大小减少25%,从2GB降至1.5GB
    • 慢查询数量减少70%,从100条/天降至30条/天
    • 应用功能正常,无业务影响

案例二:MySQL 8.0在线索引重建

场景描述

某金融系统使用MySQL 8.0,核心交易表(transactions)需要重建索引,但不能影响业务运行,要求零停机维护。

维护步骤

  1. 选择在线重建方式

    sql
    -- 使用在线重建,无锁,确保不影响业务
    ALTER TABLE transactions DROP INDEX idx_transaction_date, ADD INDEX idx_transaction_date (transaction_date) ALGORITHM=INPLACE, LOCK=NONE;
  2. 监控执行过程

    sql
    -- 查看进程状态,确保执行正常
    SHOW PROCESSLIST;
    
    -- 查看锁等待情况,确保无阻塞
    SHOW ENGINE INNODB STATUS\G
    
    -- 查看性能指标,监控资源使用
    SHOW GLOBAL STATUS LIKE 'Innodb_rows_read';
    SHOW GLOBAL STATUS LIKE 'Innodb_rows_written';
  3. 验证结果

    • 索引成功重建,无业务中断
    • 查询性能提升20%,交易响应更快
    • 写入操作正常,无数据丢失
    • 系统负载稳定,资源使用正常

常见索引维护问题及解决方案

问题解决方案
维护过程中锁表,影响业务使用在线重建索引(ALGORITHM=INPLACE, LOCK=NONE),MySQL 8.0+支持
维护时间过长,超出预期分片维护,先维护非核心表,再维护核心表;调整采样页数,减少统计信息更新时间
维护后性能下降,执行计划变差验证执行计划,手动更新统计信息;检查是否误删了有用索引
误删有用索引,导致查询性能下降维护前备份索引定义,准备回滚方案;快速重建误删的索引
索引重建失败,报错退出检查错误日志,分析失败原因;调整维护策略,如减少并发操作、增加资源等

总结

索引维护是MySQL数据库运维的重要组成部分,通过定期的索引维护可以:

  1. 提高查询性能:确保索引被有效使用,减少查询时间
  2. 降低维护成本:减少不必要的索引,降低写入操作的开销
  3. 优化存储:减少索引占用的存储空间,提高存储效率
  4. 提高写入性能:减少索引对插入、更新、删除操作的影响
  5. 确保统计信息准确:帮助查询优化器选择正确的执行计划

作为DBA,应该建立完善的索引维护机制,定期分析索引使用情况,及时删除未使用和冗余索引,定期重建核心索引,记录维护过程和结果,不断优化维护策略。

通过科学的索引维护,可以确保MySQL数据库的高性能运行,为业务提供稳定可靠的支持,尤其是在大数据量、高并发的生产环境中,索引维护更是至关重要。