Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion index-choose.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,75 @@ title: 索引的选择
category: performance
---

# 索引的选择
# 索引的选择

从存储层读取数据是 SQL 计算过程中最为耗时的部分之一,TiDB 目前支持从不同的存储和不同的索引中读取数据,索引选择得是否合理将很大程度上决定一个查询的运行速度。

本章节将介绍 TiDB 如何选择索引去读入数据,以及相关的一些控制索引选择的方式。

## 读表

在介绍索引的选择之前,首先要了解 TiDB 有哪些读表的方式,这些方式的触发条件是什么,不同方式有什么区别,各有什么优劣。

### 读表算子

| 读表算子 | 触发条件 | 适用场景 | 说明 |
| :------- | :------- | :------- | :---- |
| PointGet/BatchPointGet | 读表的范围是一个或多个单点范围 | 任何场景 | 如果能被触发,通常被认为是最快的算子,因为其直接调用 kvget 的接口进行计算,不走 coprocessor |
| TableReader | 无 | 任何场景 | 从 TiKV 端直接扫描表数据,一般被认为是效率最低的算子,除非在 `_tidb_rowid` 这一列上存在范围查询,或者无其他可以选择的读表算子时,才会选择这个算子 |
| TableReader | 表在 TiFlash 节点上存在副本 | 需要读取的列比较少,但是需要计算的行很多 | TiFlash 是列式存储,如果需要对少量的列和大量的行进行计算,一般会选择这个算子 |
| IndexReader | 表有一个或多个索引,且计算所需的列被包含在索引里 | 存在较小的索引上的范围查询,或者对索引列有顺序需求的时候 | 当存在多个索引的时候,会根据估算代价选择合理的索引 |
| IndexLookupReader | 表有一个或多个索引,且计算所需的列**不完全**被包含在索引里 | 同 IndexReader | 因为计算列不完全被包含在索引里,所以读完索引后需要回表,这里会比 IndexReader 多一些开销 |

> 注意:
>
> TableReader 是基于 `_tidb_rowid` 的索引,TiFlash 是列存索引,所以索引的选择即是读表算子的选择。

## 索引的选择

TiDB 在选择索引时,会基于每个读表算子的代价估算,在此基础上提供了启发式规则 "Skyline-Pruning",以降低错误估算导致选错索引的概率。

### Skyline-Pruning

Skyline-Pruning 是一个针对索引的启发式过滤规则,评判一个索引的好坏需要从以下三个维度进行衡量:

- 选择该索引读表时,是否需要回表(即该索引生成的计划是 IndexReader 还是 IndexLookupReader)。不用回表的索引在这个维度上优于需要回表的索引。

- 选择该索引是否能满足一定的顺序。因为索引的读取可以保证某些列集合的顺序,所以满足查询要求顺序的索引在这个维度上优于不满足的索引。

- 索引的列涵盖了多少访问条件。“访问条件”指的是可以转化为某列范围的 `where` 条件,如果某个索引的列集合涵盖的访问条件越多,那么它在这个维度上更优。

对于这三种维度,如果某个索引 `idx_a` 在**三个维度上都不比 `idx_b` 差**,且**有一个维度比 `idx_b` 好**,那么就会优先选择 `idx_a`。

### 基于代价选择

在使用 Skyline-Pruning 规则排除了不合适的索引之后,索引的选择完全基于代价估算,读表的代价估算需要考虑以下几个方面:

- 索引的每行数据在存储层的平均长度。
- 索引生成的查询范围的行数量。
- 索引的回表代价。
- 索引查询时的范围数量。

根据这些因子和代价模型,优化器会选择一个代价最低的索引进行读表。

#### 代价选择调优的常见问题

1. 估算的行数量不准确?

一般是统计信息过期或者准确度不够造成的,可以重新执行 `analyze table` 或者修改 `analyze table` 的参数。

2. 统计信息准确,为什么读 TiFlash 更快,而优化器选择了 TiKV?

目前区别 TiFlash 和 TiKV 的代价模型还比较粗糙,可以调小 `tidb_opt_seek_factor` 的值,让优化器倾向于选择 TiFlash。

3. 统计信息准确,某个索引要回表,但是它比另一个不用回表的索引实际执行更快,为什么选择了不用回表的索引?

碰到这种情况,可能是代价估算时对于回表的代价计算得过大,可以调小 `tidb_opt_network_factor`,降低回表的代价。

## 控制索引的选择

通过 [Optimizer Hints](/optimizer-hints.md) 可以实现单条查询对索引选择的控制。

- `USE_INDEX`/`IGNORE_INDEX` 可以强制优化器使用/不使用某些索引。

- `READ_FROM_STORAGE` 可以强制优化器对于某些表选择 TiKV/TiFlash 的存储引擎进行查询。