📣

TiDB Cloud Serverless 现已更名为
TiDB Cloud Starter
!体验不变,名字焕新。
立即试用 →

管理索引和识别未使用索引的最佳实践

索引对于优化数据库查询性能至关重要,能够减少大规模数据扫描的需求。然而,随着应用的演进、业务逻辑的变化以及数据规模的增长,原有的索引设计也可能会遇到问题,包括以下两类:

  • 未使用索引:这些索引曾经有用,但现在查询优化器已不再选择它们,却仍占用存储空间,并给写入操作带来不必要的开销。
  • 低效索引:某些索引虽然被优化器使用,但扫描的数据量远超预期,导致磁盘 I/O 增加,查询速度变慢。

如果不及时处理,这些索引问题会导致存储成本上升、性能下降和运维效率降低。在 TiDB 这样的分布式 SQL 数据库中,索引效率低下的影响更大,因为分布式查询规模大且多节点协同更复杂。因此,定期进行索引审计对于保持数据库优化至关重要。

主动识别并优化索引有助于:

  • 降低存储开销:移除未使用索引可释放磁盘空间,降低长期存储成本。
  • 提升写入性能:写密集型场景(如 INSERTUPDATEDELETE)下,移除不必要的索引维护可以提升性能。
  • 优化查询执行:高效的索引能够减少扫描的行数,从而加快查询速度、缩短响应时间。
  • 简化数据库管理:减少并优化索引可以简化备份、恢复和 schema 变更。

由于索引会随着业务逻辑的变化而不断演进,定期进行索引审计已成为数据库维护的标准流程。TiDB 提供了内置的可观测性手段,帮助你安全高效地观测、评估和优化索引。

TiDB v8.0.0 引入了 INFORMATION_SCHEMA.TIDB_INDEX_USAGE 表和 sys.schema_unused_indexes 表,来帮助你追踪索引使用情况,并根据运行数据做出正确判断。

本文介绍如何使用观测工具检测并移除未使用或低效索引,从而提升 TiDB 的性能与稳定性。

TiDB 索引优化:数据驱动的方法

索引对于查询性能至关重要,但如果没有充分分析就移除索引,可能导致意想不到的性能回退,甚至引发系统不稳定。为确保索引管理的安全性与有效性,TiDB 提供了内置的可观测性手段,支持以下操作:

  • 实时追踪索引使用情况:识别索引的访问频率,并判断其是否有助于性能提升。
  • 检测未使用索引:定位自数据库上次重启以来从未被使用过的索引。
  • 评估索引效率:判断索引是否能有效过滤数据,或是否导致 I/O 开销过多。
  • 安全测试索引移除:在删除索引前,可将其暂时设为不可见,确认无查询依赖该索引后再删除。

TiDB 通过以下手段简化了索引优化:

  • INFORMATION_SCHEMA.TIDB_INDEX_USAGE:监控索引使用模式和查询频率。
  • sys.schema_unused_indexes:列出自数据库上次重启以来未被使用过的索引。
  • 不可见索引:在永久删除索引之前,你可以先测试移除索引的影响。

借助这些可观测性工具,你可以放心清理冗余索引,避免性能下降风险。

使用 TIDB_INDEX_USAGE 追踪索引使用情况

TiDB v8.0.0 开始,你可以使用 TIDB_INDEX_USAGE 系统表实时观测索引使用情况,帮助你优化查询性能并移除不必要的索引。

具体来说,你可以通过 TIDB_INDEX_USAGE 进行以下操作:

  • 检测未使用索引:识别未被查询访问过的索引,帮助判断哪些索引可以安全移除。
  • 分析索引效率:追踪索引使用频率,评估其是否有助于提升查询执行效率。
  • 评估查询模式:了解索引对读操作、数据扫描和 KV 请求的影响。

TiDB v8.4.0 开始,TIDB_INDEX_USAGE 还包含聚簇表的主键,进一步提升了索引性能可见性。

TIDB_INDEX_USAGE 关键指标

如需查看 TIDB_INDEX_USAGE 系统表字段,可执行以下 SQL 语句:

USE INFORMATION_SCHEMA; DESC TIDB_INDEX_USAGE;
+--------------------------+-------------+------+------+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------------+-------------+------+------+---------+-------+ | TABLE_SCHEMA | varchar(64) | YES | | NULL | | | TABLE_NAME | varchar(64) | YES | | NULL | | | INDEX_NAME | varchar(64) | YES | | NULL | | | QUERY_TOTAL | bigint(21) | YES | | NULL | | | KV_REQ_TOTAL | bigint(21) | YES | | NULL | | | ROWS_ACCESS_TOTAL | bigint(21) | YES | | NULL | | | PERCENTAGE_ACCESS_0 | bigint(21) | YES | | NULL | | | PERCENTAGE_ACCESS_0_1 | bigint(21) | YES | | NULL | | | PERCENTAGE_ACCESS_1_10 | bigint(21) | YES | | NULL | | | PERCENTAGE_ACCESS_10_20 | bigint(21) | YES | | NULL | | | PERCENTAGE_ACCESS_20_50 | bigint(21) | YES | | NULL | | | PERCENTAGE_ACCESS_50_100 | bigint(21) | YES | | NULL | | | PERCENTAGE_ACCESS_100 | bigint(21) | YES | | NULL | | | LAST_ACCESS_TIME | datetime | YES | | NULL | | +--------------------------+-------------+------+------+---------+-------+14

关于这些列的详细说明,参见 TIDB_INDEX_USAGE

利用 TIDB_INDEX_USAGE 识别未使用和低效索引

本节介绍如何利用 TIDB_INDEX_USAGE 系统表识别未使用和低效索引。

  • 未使用索引:

    • 如果 QUERY_TOTAL = 0,说明该索引未被任何查询使用。
    • 如果 LAST_ACCESS_TIME 显示的时间距今较久,说明该索引可能已无用。
  • 低效索引:

    • 如果 PERCENTAGE_ACCESS_100 数值较大,说明存在全索引扫描,可能为低效索引。
    • 对比 ROWS_ACCESS_TOTALQUERY_TOTAL,判断索引在查询中扫描的行数是否过多。

通过 TIDB_INDEX_USAGE 系统表,你可以详细了解索引性能,以便移除冗余索引、优化查询执行。

高效使用 TIDB_INDEX_USAGE

以下要点可以帮助你正确理解并使用 TIDB_INDEX_USAGE 系统表。

数据更新存在延迟

为降低性能影响,TIDB_INDEX_USAGE 的数据并非实时更新,索引使用指标可能会有最多 5 分钟的延迟。在分析查询时,需要考虑这一延迟。

索引使用数据不持久化

TIDB_INDEX_USAGE 系统表的数据存储在每个 TiDB 实例的内存中,不会持久化,在节点重启后数据会丢失。

跟踪历史数据

你可以定期使用以下 SQL 语句导出索引使用快照:

SELECT * FROM INFORMATION_SCHEMA.TIDB_INDEX_USAGE INTO OUTFILE '/backup/index_usage_snapshot.csv';

通过比较不同时间的快照,可以进行历史追踪,帮助你发现索引使用趋势,从而做出更有依据的索引优化或清理决策。

使用 CLUSTER_TIDB_INDEX_USAGE 汇总全集群索引数据

由于 TiDB 是分布式 SQL 数据库,查询负载会分布于多个节点。每个 TiDB 节点独立追踪本地索引使用情况。为获得全局索引性能,TiDB 提供了 CLUSTER_TIDB_INDEX_USAGE 系统表,汇总了所有节点的索引使用数据,确保在优化索引策略时充分考虑分布式查询负载。

不同 TiDB 节点的查询负载可能不同。某个索引在部分节点看似未被使用,但在其他节点可能仍然至关重要。如果要按查询负载对索引分析进行划分,执行以下 SQL 语句:

SELECT INSTANCE, TABLE_NAME, INDEX_NAME, SUM(QUERY_TOTAL) AS total_queries FROM INFORMATION_SCHEMA.CLUSTER_TIDB_INDEX_USAGE GROUP BY INSTANCE, TABLE_NAME, INDEX_NAME ORDER BY total_queries DESC;

这样可以帮助判断一个索引是在所有节点上都未被使用,还是仅在特定节点未被使用,从而帮助你在删除索引时做出更明智的决策。

TIDB_INDEX_USAGECLUSTER_TIDB_INDEX_USAGE 的区别

TIDB_INDEX_USAGECLUSTER_TIDB_INDEX_USAGE 的区别如下表所示:

功能TIDB_INDEX_USAGECLUSTER_TIDB_INDEX_USAGE
作用范围追踪单个数据库实例内的索引使用情况汇总整个 TiDB 集群的索引使用情况
索引追踪数据为本地实例级提供集群级统一视图
主要用途调试数据库实例级的索引使用情况分析全局索引模式及多节点行为

高效使用 CLUSTER_TIDB_INDEX_USAGE

由于 CLUSTER_TIDB_INDEX_USAGE 系统表汇总了多个节点的数据,使用时需注意以下事项:

  • 数据更新存在延迟

    为将对性能的影响降到最低,CLUSTER_TIDB_INDEX_USAGE 不会实时更新。索引使用指标可能会有最多 5 分钟的延迟。在分析查询时,需要考虑这一延迟。

  • 内存存储

    TIDB_INDEX_USAGE 一样,在节点重启后,储存在内存中的索引使用数据会丢失。

通过 CLUSTER_TIDB_INDEX_USAGE,可获得全局视角的索引行为,确保索引策略与分布式查询负载匹配。

使用 schema_unused_indexes 快速识别未使用索引

手动分析索引使用数据可能会非常耗时。为简化该过程,TiDB 提供了 schema_unused_indexes 系统视图,用于列出自数据库上次重启以来未被使用过的索引。

使用该视图,你可以:

  • 快速识别不再使用的索引,降低不必要的存储成本。
  • 通过移除无用索引,加速 DML 操作(如 INSERTUPDATEDELETE)。
  • 无需手动分析查询模式,简化索引审计。

通过使用 schema_unused_indexes,你可以快速识别不必要的索引,轻松降低数据库开销。

schema_unused_indexes 的工作原理

schema_unused_indexes 视图基于 TIDB_INDEX_USAGE,可以自动筛选出自上次 TiDB 重启以来查询次数为零的索引。

查询未使用索引:

SELECT * FROM sys.schema_unused_indexes;

返回类似如下结果:

+-----------------+---------------+--------------------+ | object_schema | object_name | index_name | +---------------- + ------------- + -------------------+ | bookshop | users | nickname | | bookshop | ratings | uniq_book_user_idx | +---------------- + ------------- + -------------------+

使用 schema_unused_indexes 的注意事项

使用 schema_unused_indexes 时,需注意以下事项。

仅统计自上次重启以来的未使用索引

  • 如果 TiDB 节点重启,索引使用数据会被重置。
  • 在使用索引数据之前,确保系统已经运行了足够长的时间,以便捕获具有代表性的业务负载。

并非所有未使用索引都可立即删除

有些索引可能使用频率很低,但对于特定查询、批处理或报表任务仍然至关重要。在删除索引之前,请考虑它是否支持以下场景:

  • 低频但关键的查询,如月度报表、分析任务
  • 非每日运行的批处理作业
  • 临时排查故障用的查询

如果索引在重要但不频繁的查询中出现,建议先保留或设为不可见。

你可以使用不可见索引来安全地验证某个索引是否可以删除,而不会影响系统性能。

手动创建 schema_unused_indexes 视图

对于从较早版本升级到 TiDB v8.0.0 或更高版本的集群,需要手动创建系统 schema 及相关视图。

详见手动创建 schema_unused_indexes 视图

使用不可见索引安全测试索引移除

在未经充分验证的情况下直接删除索引可能带来性能风险,尤其是那些虽不常用但对特定查询仍至关重要的索引。

为降低风险,TiDB 提供了不可见索引,可以临时禁用索引但不删除。通过使用不可见索引,你可以安全地验证索引删除决策,确保数据库优化过程更加可控和可预测。

什么是不可见索引?

不可见索引仍然存在于数据库中,但 TiDB 优化器会忽略它。你可以使用 ALTER TABLE ... INVISIBLE 将索引设为不可见,从而在不永久删除索引的情况下测试该索引是否真的无用。

不可见索引的主要优势如下:

  • 安全测试索引:查询将不再使用该索引,但相关的优化器统计信息仍然会被维护,必要时可随时快速恢复。
  • 不影响索引存储:索引结构未变,无需重新创建,避免额外开销。
  • 性能监控:数据库管理员可观察禁用索引后的查询表现,辅助决策。

设置索引为不可见

要在不删除索引的情况下使其不可见,可执行以下 SQL 语句:

ALTER TABLE bookshop.users ALTER INDEX nickname INVISIBLE;

设置索引为不可见后,观察系统的查询性能:

  • 如果性能保持不变,说明该索引可能不必要,可以安全删除。
  • 如果查询延迟增加,说明该索引可能仍有必要保留。

高效使用不可见索引

  • 建议在业务低峰期测试:便于在可控环境中监控对性能的影响。
  • 结合查询监控工具:对比分析索引可见与不可见时的执行计划。
  • 在多种业务场景进行验证:确保索引不被特定报表或定时任务依赖。

不可见索引建议保留多久?

  • OLTP 负载:建议至少观察一周,覆盖日常波动。
  • 批处理或 ETL 负载:建议覆盖一个完整的报表周期,如月度财务报表。
  • 临时分析查询:结合查询日志确认不依赖该索引后再删除。

安全起见,建议至少保持索引在一个完整业务周期内处于不可见状态,以确保所有工作负载都已经过验证,再做最终决定。

索引优化的五大最佳实践

为了保持高性能和高效的资源利用,定期进行索引优化是数据库运维的常规工作。以下是 TiDB 中有效管理索引的最佳实践:

  1. 定期监控索引使用。

  2. 在删除索引前,务必进行验证。

    • 使用 ALTER TABLE ... INVISIBLE 将索引设为不可见,临时禁用索引,观察影响后再决定是否永久删除。
    • 若查询性能保持稳定,可考虑删除索引。
    • 确保有足够的观察周期,以覆盖所有业务场景或查询模式后再做最终决策。
  3. 优化现有索引。

    • 合并冗余索引。合并冗余索引可以减少存储开销、提升写入性能。如果多个索引服务于相似的查询,可以考虑将它们合并为单个更高效的复合索引。

      • 执行以下 SQL 语句,查找前缀重叠的索引(表明可能存在冗余):

        SELECT TABLE_SCHEMA, TABLE_NAME, INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_NAME = 'your_table' ORDER BY TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, SEQ_IN_INDEX;
      • 若两个索引的前导列相同,建议考虑将它们合并为复合索引。

    • 提高选择性。可以通过以下方式优化低选择性索引(即那些过滤行数过多的索引):

      • 增加额外的列,以提升过滤效率。
      • 调整索引结构(如前缀索引、复合索引)。
    • 分析索引选择性。利用 TIDB_INDEX_USAGEPERCENTAGE_ACCESS_* 字段评估索引过滤数据的效果。

  4. 关注 DML 性能影响。

    • 避免过度索引。每增加一个索引,INSERTUPDATEDELETE 操作的开销都会增加。
    • 仅为查询所必需的字段建立索引,以减少写入密集型负载的维护成本。
  5. 定期测试与调优。

    • 定期进行索引审计,尤其在业务负载发生重大变化后。
    • 利用 TiDB 执行计划分析工具,验证索引是否被高效使用。
    • 新增索引时,建议先在隔离环境中测试,避免出现意外的性能回退。

通过遵循以上这些最佳实践,你可以确保查询高效执行,减少不必要的存储开销,并保持数据库性能最优。

文档内容是否有帮助?