|
| 1 | +--- |
| 2 | +title: 管理索引和识别未使用索引的最佳实践 |
| 3 | +summary: 了解在 TiDB 中管理和优化索引、识别并移除未使用索引的最佳实践。 |
| 4 | +--- |
| 5 | + |
| 6 | +# 管理索引和识别未使用索引的最佳实践 |
| 7 | + |
| 8 | +索引对于优化数据库查询性能至关重要,能够减少大规模数据扫描的需求。然而,随着应用的演进、业务逻辑的变化以及数据规模的增长,原有的索引设计也可能会遇到问题,包括以下两类: |
| 9 | + |
| 10 | +- 未使用索引:这些索引曾经有用,但现在查询优化器已不再选择它们,却仍占用存储空间,并给写入操作带来不必要的开销。 |
| 11 | +- 低效索引:某些索引虽然被优化器使用,但扫描的数据量远超预期,导致磁盘 I/O 增加,查询速度变慢。 |
| 12 | + |
| 13 | +如果不及时处理,这些索引问题会导致存储成本上升、性能下降和运维效率降低。在 TiDB 这样的分布式 SQL 数据库中,索引效率低下的影响更大,因为分布式查询规模大且多节点协同更复杂。因此,定期进行索引审计对于保持数据库优化至关重要。 |
| 14 | + |
| 15 | +主动识别并优化索引有助于: |
| 16 | + |
| 17 | +- 降低存储开销:移除未使用索引可释放磁盘空间,降低长期存储成本。 |
| 18 | +- 提升写入性能:写密集型场景(如 `INSERT`、`UPDATE`、`DELETE`)下,移除不必要的索引维护可以提升性能。 |
| 19 | +- 优化查询执行:高效的索引能够减少扫描的行数,从而加快查询速度、缩短响应时间。 |
| 20 | +- 简化数据库管理:减少并优化索引可以简化备份、恢复和 schema 变更。 |
| 21 | + |
| 22 | +由于索引会随着业务逻辑的变化而不断演进,定期进行索引审计已成为数据库维护的标准流程。TiDB 提供了内置的可观测性手段,帮助你安全高效地观测、评估和优化索引。 |
| 23 | + |
| 24 | +TiDB v8.0.0 引入了 [`INFORMATION_SCHEMA.TIDB_INDEX_USAGE`](/information-schema/information-schema-tidb-index-usage.md) 表和 [`sys.schema_unused_indexes`](/sys-schema/sys-schema-unused-indexes.md) 表,来帮助你追踪索引使用情况,并根据运行数据做出正确判断。 |
| 25 | + |
| 26 | +本文介绍如何使用观测工具检测并移除未使用或低效索引,从而提升 TiDB 的性能与稳定性。 |
| 27 | + |
| 28 | +## TiDB 索引优化:数据驱动的方法 |
| 29 | + |
| 30 | +索引对于查询性能至关重要,但如果没有充分分析就移除索引,可能导致意想不到的性能回退,甚至引发系统不稳定。为确保索引管理的安全性与有效性,TiDB 提供了内置的可观测性手段,支持以下操作: |
| 31 | + |
| 32 | +- 实时追踪索引使用情况:识别索引的访问频率,并判断其是否有助于性能提升。 |
| 33 | +- 检测未使用索引:定位自数据库上次重启以来从未被使用过的索引。 |
| 34 | +- 评估索引效率:判断索引是否能有效过滤数据,或是否导致 I/O 开销过多。 |
| 35 | +- 安全测试索引移除:在删除索引前,可将其暂时设为不可见,确认无查询依赖该索引后再删除。 |
| 36 | + |
| 37 | +TiDB 通过以下手段简化了索引优化: |
| 38 | + |
| 39 | +- `INFORMATION_SCHEMA.TIDB_INDEX_USAGE`:监控索引使用模式和查询频率。 |
| 40 | +- `sys.schema_unused_indexes`:列出自数据库上次重启以来未被使用过的索引。 |
| 41 | +- 不可见索引:在永久删除索引之前,你可以先测试移除索引的影响。 |
| 42 | + |
| 43 | +借助这些可观测性工具,你可以放心清理冗余索引,避免性能下降风险。 |
| 44 | + |
| 45 | +## 使用 `TIDB_INDEX_USAGE` 追踪索引使用情况 |
| 46 | + |
| 47 | +从 [TiDB v8.0.0](/releases/release-8.0.0.md) 开始,你可以使用 `TIDB_INDEX_USAGE` 系统表实时观测索引使用情况,帮助你优化查询性能并移除不必要的索引。 |
| 48 | + |
| 49 | +具体来说,你可以通过 `TIDB_INDEX_USAGE` 进行以下操作: |
| 50 | + |
| 51 | +- 检测未使用索引:识别未被查询访问过的索引,帮助判断哪些索引可以安全移除。 |
| 52 | +- 分析索引效率:追踪索引使用频率,评估其是否有助于提升查询执行效率。 |
| 53 | +- 评估查询模式:了解索引对读操作、数据扫描和 KV 请求的影响。 |
| 54 | + |
| 55 | +从 [TiDB v8.4.0](/releases/release-8.4.0.md) 开始,`TIDB_INDEX_USAGE` 还包含聚簇表的主键,进一步提升了索引性能可见性。 |
| 56 | + |
| 57 | +### `TIDB_INDEX_USAGE` 关键指标 |
| 58 | + |
| 59 | +如需查看 `TIDB_INDEX_USAGE` 系统表字段,可执行以下 SQL 语句: |
| 60 | + |
| 61 | +```sql |
| 62 | +USE INFORMATION_SCHEMA; |
| 63 | +DESC TIDB_INDEX_USAGE; |
| 64 | +``` |
| 65 | + |
| 66 | +```sql |
| 67 | ++--------------------------+-------------+------+------+---------+-------+ |
| 68 | +| Field | Type | Null | Key | Default | Extra | |
| 69 | ++--------------------------+-------------+------+------+---------+-------+ |
| 70 | +| TABLE_SCHEMA | varchar(64) | YES | | NULL | | |
| 71 | +| TABLE_NAME | varchar(64) | YES | | NULL | | |
| 72 | +| INDEX_NAME | varchar(64) | YES | | NULL | | |
| 73 | +| QUERY_TOTAL | bigint(21) | YES | | NULL | | |
| 74 | +| KV_REQ_TOTAL | bigint(21) | YES | | NULL | | |
| 75 | +| ROWS_ACCESS_TOTAL | bigint(21) | YES | | NULL | | |
| 76 | +| PERCENTAGE_ACCESS_0 | bigint(21) | YES | | NULL | | |
| 77 | +| PERCENTAGE_ACCESS_0_1 | bigint(21) | YES | | NULL | | |
| 78 | +| PERCENTAGE_ACCESS_1_10 | bigint(21) | YES | | NULL | | |
| 79 | +| PERCENTAGE_ACCESS_10_20 | bigint(21) | YES | | NULL | | |
| 80 | +| PERCENTAGE_ACCESS_20_50 | bigint(21) | YES | | NULL | | |
| 81 | +| PERCENTAGE_ACCESS_50_100 | bigint(21) | YES | | NULL | | |
| 82 | +| PERCENTAGE_ACCESS_100 | bigint(21) | YES | | NULL | | |
| 83 | +| LAST_ACCESS_TIME | datetime | YES | | NULL | | |
| 84 | ++--------------------------+-------------+------+------+---------+-------+ |
| 85 | +共 14 行 |
| 86 | +``` |
| 87 | + |
| 88 | +关于这些列的详细说明,参见 [`TIDB_INDEX_USAGE`](/information-schema/information-schema-tidb-index-usage.md)。 |
| 89 | + |
| 90 | +### 利用 `TIDB_INDEX_USAGE` 识别未使用和低效索引 |
| 91 | + |
| 92 | +本节介绍如何利用 `TIDB_INDEX_USAGE` 系统表识别未使用和低效索引。 |
| 93 | + |
| 94 | +- 未使用索引: |
| 95 | + |
| 96 | + - 如果 `QUERY_TOTAL = 0`,说明该索引未被任何查询使用。 |
| 97 | + - 如果 `LAST_ACCESS_TIME` 显示的时间距今较久,说明该索引可能已无用。 |
| 98 | + |
| 99 | +- 低效索引: |
| 100 | + |
| 101 | + - 如果 `PERCENTAGE_ACCESS_100` 数值较大,说明存在全索引扫描,可能为低效索引。 |
| 102 | + - 对比 `ROWS_ACCESS_TOTAL` 与 `QUERY_TOTAL`,判断索引在查询中扫描的行数是否过多。 |
| 103 | + |
| 104 | +通过 `TIDB_INDEX_USAGE` 系统表,你可以详细了解索引性能,以便移除冗余索引、优化查询执行。 |
| 105 | + |
| 106 | +### 高效使用 `TIDB_INDEX_USAGE` |
| 107 | + |
| 108 | +以下要点可以帮助你正确理解并使用 `TIDB_INDEX_USAGE` 系统表。 |
| 109 | + |
| 110 | +#### 数据更新存在延迟 |
| 111 | + |
| 112 | +为降低性能影响,`TIDB_INDEX_USAGE` 的数据并非实时更新,索引使用指标可能会有最多 5 分钟的延迟。在分析查询时,需要考虑这一延迟。 |
| 113 | + |
| 114 | +#### 索引使用数据不持久化 |
| 115 | + |
| 116 | +`TIDB_INDEX_USAGE` 系统表的数据存储在每个 TiDB 实例的内存中,不会持久化,在节点重启后数据会丢失。 |
| 117 | + |
| 118 | +#### 跟踪历史数据 |
| 119 | + |
| 120 | +你可以定期使用以下 SQL 语句导出索引使用快照: |
| 121 | + |
| 122 | +```sql |
| 123 | +SELECT * FROM INFORMATION_SCHEMA.TIDB_INDEX_USAGE INTO OUTFILE '/backup/index_usage_snapshot.csv'; |
| 124 | +``` |
| 125 | + |
| 126 | +通过比较不同时间的快照,可以进行历史追踪,帮助你发现索引使用趋势,从而做出更有依据的索引优化或清理决策。 |
| 127 | + |
| 128 | +## 使用 `CLUSTER_TIDB_INDEX_USAGE` 汇总全集群索引数据 |
| 129 | + |
| 130 | +由于 TiDB 是分布式 SQL 数据库,查询负载会分布于多个节点。每个 TiDB 节点独立追踪本地索引使用情况。为获得全局索引性能,TiDB 提供了 [`CLUSTER_TIDB_INDEX_USAGE`](/information-schema/information-schema-tidb-index-usage.md#cluster_tidb_index_usage) 系统表,汇总了所有节点的索引使用数据,确保在优化索引策略时充分考虑分布式查询负载。 |
| 131 | + |
| 132 | +不同 TiDB 节点的查询负载可能不同。某个索引在部分节点看似未被使用,但在其他节点可能仍然至关重要。如果要按查询负载对索引分析进行划分,执行以下 SQL 语句: |
| 133 | + |
| 134 | +```sql |
| 135 | +SELECT INSTANCE, TABLE_NAME, INDEX_NAME, SUM(QUERY_TOTAL) AS total_queries |
| 136 | +FROM INFORMATION_SCHEMA.CLUSTER_TIDB_INDEX_USAGE |
| 137 | +GROUP BY INSTANCE, TABLE_NAME, INDEX_NAME |
| 138 | +ORDER BY total_queries DESC; |
| 139 | +``` |
| 140 | + |
| 141 | +这样可以帮助判断一个索引是在所有节点上都未被使用,还是仅在特定节点未被使用,从而帮助你在删除索引时做出更明智的决策。 |
| 142 | + |
| 143 | +### `TIDB_INDEX_USAGE` 与 `CLUSTER_TIDB_INDEX_USAGE` 的区别 |
| 144 | + |
| 145 | +`TIDB_INDEX_USAGE` 与 `CLUSTER_TIDB_INDEX_USAGE` 的区别如下表所示: |
| 146 | + |
| 147 | +| 功能 | `TIDB_INDEX_USAGE` | `CLUSTER_TIDB_INDEX_USAGE` | |
| 148 | +| -------------- | ----------------------------------------- | -------------------------------------------- | |
| 149 | +| 作用范围 | 追踪单个数据库实例内的索引使用情况 | 汇总整个 TiDB 集群的索引使用情况 | |
| 150 | +| 索引追踪 | 数据为本地实例级 | 提供集群级统一视图 | |
| 151 | +| 主要用途 | 调试数据库实例级的索引使用情况 | 分析全局索引模式及多节点行为 | |
| 152 | + |
| 153 | +### 高效使用 `CLUSTER_TIDB_INDEX_USAGE` |
| 154 | + |
| 155 | +由于 `CLUSTER_TIDB_INDEX_USAGE` 系统表汇总了多个节点的数据,使用时需注意以下事项: |
| 156 | + |
| 157 | +- 数据更新存在延迟 |
| 158 | + |
| 159 | + 为将对性能的影响降到最低,`CLUSTER_TIDB_INDEX_USAGE` 不会实时更新。索引使用指标可能会有最多 5 分钟的延迟。在分析查询时,需要考虑这一延迟。 |
| 160 | + |
| 161 | +- 内存存储 |
| 162 | + |
| 163 | + 与 `TIDB_INDEX_USAGE` 一样,在节点重启后,储存在内存中的索引使用数据会丢失。 |
| 164 | + |
| 165 | +通过 `CLUSTER_TIDB_INDEX_USAGE`,可获得全局视角的索引行为,确保索引策略与分布式查询负载匹配。 |
| 166 | + |
| 167 | +## 使用 `schema_unused_indexes` 快速识别未使用索引 |
| 168 | + |
| 169 | +手动分析索引使用数据可能会非常耗时。为简化该过程,TiDB 提供了 [`schema_unused_indexes`](/sys-schema/sys-schema-unused-indexes.md) 系统视图,用于列出自数据库上次重启以来未被使用过的索引。 |
| 170 | + |
| 171 | +使用该视图,你可以: |
| 172 | + |
| 173 | +- 快速识别不再使用的索引,降低不必要的存储成本。 |
| 174 | +- 通过移除无用索引,加速 DML 操作(如 `INSERT`、`UPDATE`、`DELETE`)。 |
| 175 | +- 无需手动分析查询模式,简化索引审计。 |
| 176 | + |
| 177 | +通过使用 `schema_unused_indexes`,你可以快速识别不必要的索引,轻松降低数据库开销。 |
| 178 | + |
| 179 | +### `schema_unused_indexes` 的工作原理 |
| 180 | + |
| 181 | +`schema_unused_indexes` 视图基于 `TIDB_INDEX_USAGE`,可以自动筛选出自上次 TiDB 重启以来查询次数为零的索引。 |
| 182 | + |
| 183 | +查询未使用索引: |
| 184 | + |
| 185 | +```sql |
| 186 | +SELECT * FROM sys.schema_unused_indexes; |
| 187 | +``` |
| 188 | + |
| 189 | +返回类似如下结果: |
| 190 | + |
| 191 | +``` |
| 192 | ++-----------------+---------------+--------------------+ |
| 193 | +| object_schema | object_name | index_name | |
| 194 | ++---------------- + ------------- + -------------------+ |
| 195 | +| bookshop | users | nickname | |
| 196 | +| bookshop | ratings | uniq_book_user_idx | |
| 197 | ++---------------- + ------------- + -------------------+ |
| 198 | +``` |
| 199 | + |
| 200 | +### 使用 `schema_unused_indexes` 的注意事项 |
| 201 | + |
| 202 | +使用 `schema_unused_indexes` 时,需注意以下事项。 |
| 203 | + |
| 204 | +#### 仅统计自上次重启以来的未使用索引 |
| 205 | + |
| 206 | +- 如果 TiDB 节点重启,索引使用数据会被重置。 |
| 207 | +- 在使用索引数据之前,确保系统已经运行了足够长的时间,以便捕获具有代表性的业务负载。 |
| 208 | + |
| 209 | +#### 并非所有未使用索引都可立即删除 |
| 210 | + |
| 211 | +有些索引可能使用频率很低,但对于特定查询、批处理或报表任务仍然至关重要。在删除索引之前,请考虑它是否支持以下场景: |
| 212 | + |
| 213 | +- 低频但关键的查询,如月度报表、分析任务 |
| 214 | +- 非每日运行的批处理作业 |
| 215 | +- 临时排查故障用的查询 |
| 216 | + |
| 217 | +如果索引在重要但不频繁的查询中出现,建议先保留或设为不可见。 |
| 218 | + |
| 219 | +你可以使用[不可见索引](#使用不可见索引安全移除测试索引)来安全地验证某个索引是否可以删除,而不会影响系统性能。 |
| 220 | + |
| 221 | +### 手动创建 `schema_unused_indexes` 视图 |
| 222 | + |
| 223 | +对于从较早版本升级到 TiDB v8.0.0 或更高版本的集群,需要手动创建系统 schema 及相关视图。 |
| 224 | + |
| 225 | +详见[手动创建 `schema_unused_indexes` 视图](/sys-schema/sys-schema-unused-indexes.md#手动创建-schema_unused_indexes-视图)。 |
| 226 | + |
| 227 | +## 使用不可见索引安全移除测试索引 |
| 228 | + |
| 229 | +在未经充分验证的情况下直接删除索引可能带来性能风险,尤其是那些虽不常用但对特定查询仍至关重要的索引。 |
| 230 | + |
| 231 | +为降低风险,TiDB 提供了不可见索引,可以临时禁用索引但不删除。通过使用不可见索引,你可以安全地验证索引删除决策,确保数据库优化过程更加可控和可预测。 |
| 232 | + |
| 233 | +### 什么是不可见索引? |
| 234 | + |
| 235 | +不可见索引仍然存在于数据库中,但 TiDB 优化器会忽略它。你可以使用 [`ALTER TABLE ... INVISIBLE`](/sql-statements/sql-statement-alter-table.md) 将索引设为不可见,从而在不永久删除索引的情况下测试该索引是否真的无用。 |
| 236 | + |
| 237 | +不可见索引的主要优势如下: |
| 238 | + |
| 239 | +- **安全测试索引**:查询将不再使用该索引,但相关的优化器统计信息仍然会被维护,必要时可随时快速恢复。 |
| 240 | +- **不影响索引存储**:索引结构未变,无需重新创建,避免额外开销。 |
| 241 | +- **性能监控**:数据库管理员可观察禁用索引后的查询表现,辅助决策。 |
| 242 | + |
| 243 | +### 设置索引为不可见 |
| 244 | + |
| 245 | +要在不删除索引的情况下使其不可见,可执行以下 SQL 语句: |
| 246 | + |
| 247 | +```sql |
| 248 | +ALTER TABLE bookshop.users ALTER INDEX nickname INVISIBLE; |
| 249 | +``` |
| 250 | + |
| 251 | +设置索引为不可见后,观察系统的查询性能: |
| 252 | + |
| 253 | +- 如果性能保持不变,说明该索引可能不必要,可以安全删除。 |
| 254 | +- 如果查询延迟增加,说明该索引可能仍有必要保留。 |
| 255 | + |
| 256 | +### 高效使用不可见索引 |
| 257 | + |
| 258 | +- **建议在业务低峰期测试**:便于在可控环境中监控对性能的影响。 |
| 259 | +- **结合查询监控工具**:对比分析索引可见与不可见时的执行计划。 |
| 260 | +- **在多种业务场景进行验证**:确保索引不被特定报表或定时任务依赖。 |
| 261 | + |
| 262 | +### 不可见索引建议保留多久? |
| 263 | + |
| 264 | +- OLTP 负载:建议至少观察一周,覆盖日常波动。 |
| 265 | +- 批处理或 ETL 负载:建议覆盖一个完整的报表周期,如月度财务报表。 |
| 266 | +- 临时分析查询:结合查询日志确认不依赖该索引后再删除。 |
| 267 | + |
| 268 | +安全起见,建议至少保持索引在一个完整业务周期内处于不可见状态,以确保所有工作负载都已经过验证,再做最终决定。 |
| 269 | + |
| 270 | +## 索引优化的五大最佳实践 |
| 271 | + |
| 272 | +为了保持高性能和高效的资源利用,定期进行索引优化是数据库运维的常规工作。以下是 TiDB 中有效管理索引的最佳实践: |
| 273 | + |
| 274 | +1. **定期监控索引使用。** |
| 275 | + |
| 276 | + - 使用 [`TIDB_INDEX_USAGE`](/information-schema/information-schema-tidb-index-usage.md) 和 [`CLUSTER_TIDB_INDEX_USAGE`](/information-schema/information-schema-tidb-index-usage.md#cluster_tidb_index_usage) 追踪索引使用情况。 |
| 277 | + - 通过 [`schema_unused_indexes`](/sys-schema/sys-schema-unused-indexes.md) 识别未使用的索引,并评估是否可删除。 |
| 278 | + - 监控查询执行计划,识别可能导致高 I/O 的低效索引。 |
| 279 | + |
| 280 | +2. **在删除索引前,务必进行验证。** |
| 281 | + |
| 282 | + - 使用 [`ALTER TABLE ... INVISIBLE`](/sql-statements/sql-statement-alter-table.md) 将索引设为不可见,临时禁用索引,观察影响后再决定是否永久删除。 |
| 283 | + - 若查询性能保持稳定,可考虑删除索引。 |
| 284 | + - 确保有足够的观察周期,以覆盖所有业务场景或查询模式后再做最终决策。 |
| 285 | + |
| 286 | +3. **优化现有索引。** |
| 287 | + |
| 288 | + - 合并冗余索引。合并冗余索引可以减少存储开销、提升写入性能。如果多个索引服务于相似的查询,可以考虑将它们合并为单个更高效的复合索引。 |
| 289 | + |
| 290 | + - 执行以下 SQL 语句,查找前缀重叠的索引(表明可能存在冗余): |
| 291 | + |
| 292 | + ```sql |
| 293 | + SELECT TABLE_SCHEMA, TABLE_NAME, INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX |
| 294 | + FROM INFORMATION_SCHEMA.STATISTICS |
| 295 | + WHERE TABLE_NAME = 'your_table' |
| 296 | + ORDER BY TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, SEQ_IN_INDEX; |
| 297 | + ``` |
| 298 | + |
| 299 | + - 若两个索引的前导列相同,建议考虑将它们合并为复合索引。 |
| 300 | + |
| 301 | + - 提高选择性。可以通过以下方式优化低选择性索引(即那些过滤行数过多的索引): |
| 302 | + |
| 303 | + - 增加额外的列,以提升过滤效率。 |
| 304 | + - 调整索引结构(如前缀索引、复合索引)。 |
| 305 | + |
| 306 | + - 分析索引选择性。利用 `TIDB_INDEX_USAGE` 的 `PERCENTAGE_ACCESS_*` 字段评估索引过滤数据的效果。 |
| 307 | + |
| 308 | +4. **关注 DML 性能影响。** |
| 309 | + |
| 310 | + - 避免过度索引。每增加一个索引,`INSERT`、`UPDATE` 和 `DELETE` 操作的开销都会增加。 |
| 311 | + - 仅为查询所必需的字段建立索引,以减少写入密集型负载的维护成本。 |
| 312 | + |
| 313 | +5. **定期测试与调优。** |
| 314 | + |
| 315 | + - 定期进行索引审计,尤其在业务负载发生重大变化后。 |
| 316 | + - 利用 TiDB 执行计划分析工具,验证索引是否被高效使用。 |
| 317 | + - 新增索引时,建议先在隔离环境中测试,避免出现意外的性能回退。 |
| 318 | + |
| 319 | +通过遵循以上这些最佳实践,你可以确保查询高效执行,减少不必要的存储开销,并保持数据库性能最优。 |
0 commit comments