Skip to content

Commit 75a8861

Browse files
authored
best practices: add index best practices (#20903)
1 parent e7fee4a commit 75a8861

File tree

2 files changed

+320
-0
lines changed

2 files changed

+320
-0
lines changed

TOC.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@
428428
- [TiDB 最佳实践](/best-practices/tidb-best-practices.md)
429429
- [DDL 最佳实践](/ddl-introduction.md)
430430
- [多列索引优化最佳实践](/best-practices/multi-column-index-best-practices.md)
431+
- [管理索引和识别未使用索引的最佳实践](/best-practices/index-management-best-practices.md)
431432
- [SaaS 多租户场景下处理百万张表](/best-practices/saas-best-practices.md)
432433
- [将 UUID 用作主键的最佳实践](/best-practices/uuid.md)
433434
- [Java 应用开发最佳实践](/best-practices/java-app-best-practices.md)
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
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

Comments
 (0)