From afe0e5372114aab95388613fba32cfb3e84c7aee Mon Sep 17 00:00:00 2001 From: Yifan Xu <30385241+xuyifangreeneyes@users.noreply.github.com> Date: Thu, 12 Aug 2021 13:35:16 +0800 Subject: [PATCH] Update description on rule based index selection (#6815) --- choose-index.md | 76 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/choose-index.md b/choose-index.md index d5a55b893b69..a7fd3ea7b6ad 100644 --- a/choose-index.md +++ b/choose-index.md @@ -29,19 +29,83 @@ aliases: ['/docs-cn/dev/choose-index/'] ## 索引的选择 -TiDB 在选择索引时,会基于每个读表算子的代价估算,在此基础上提供了启发式规则 "Skyline-Pruning",以降低错误估算导致选错索引的概率。 +TiDB 基于规则或基于代价来选择索引。基于的规则包括前置规则和 Skyline-Pruning。在选择索引时,TiDB 会先尝试前置规则。如果存在索引满足某一条前置规则,则直接选择该索引。否则,TiDB 会采用 Skyline-Pruning 来排除不合适的索引,然后基于每个读表算子的代价估算,选择代价最小的索引。 -### Skyline-Pruning +### 基于规则选择 -Skyline-Pruning 是一个针对索引的启发式过滤规则,评判一个索引的好坏需要从以下三个维度进行衡量: +#### 前置规则 -- 选择该索引读表时,是否需要回表(即该索引生成的计划是 IndexReader 还是 IndexLookupReader)。不用回表的索引在这个维度上优于需要回表的索引。 +TiDB 采用如下的启发式前置规则来选择索引: -- 选择该索引是否能满足一定的顺序。因为索引的读取可以保证某些列集合的顺序,所以满足查询要求顺序的索引在这个维度上优于不满足的索引。 ++ 规则 1:如果存在索引满足“唯一性索引全匹配 + 不需要回表(即该索引生成的计划是 IndexReader)”时,直接选择该索引。 + ++ 规则 2:如果存在索引满足“唯一性索引 + 需要回表(即该索引生成的计划是 IndexLookupReader)”时,选择满足该条件且回表行数最小的索引作为候选索引。 + ++ 规则 3:如果存在索引满足“普通索引不需要回表 + 读取行数小于一定阈值”时,选择满足该条件且读取行数最小的索引作为候选索引。 + ++ 规则 4:如果规则 2 和 3 之中仅选出一条候选索引,则选择该候选索引。如果规则 2 和 3 均选出候选索引,则选择读取行数(读索引行数 + 回表行数)较小的索引。 + +上述规则中的“索引全匹配”指每个索引列上均存在等值条件。在执行 `EXPLAIN FORMAT = 'verbose' ...` 语句时,如果前置规则匹配了某一索引,TiDB 会输出一条 NOTE 级别的 warning 提示该索引匹配了前置规则。 + +在以下示例中,因为索引 `idx_b` 满足规则 2 中“唯一性索引 + 需要回表”的条件,TiDB 选择索引 `idx_b` 作为访问路径,`SHOW WARNING` 返回了索引 `idx_b` 命中前置规则的提示。 + +```sql +mysql> CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE INDEX idx_b(b)); +Query OK, 0 rows affected (0.01 sec) + +mysql> EXPLAIN FORMAT = 'verbose' SELECT b, c FROM t WHERE b = 3 OR b = 6; ++-------------------+---------+---------+------+-------------------------+------------------------------+ +| id | estRows | estCost | task | access object | operator info | ++-------------------+---------+---------+------+-------------------------+------------------------------+ +| Batch_Point_Get_5 | 2.00 | 8.80 | root | table:t, index:idx_b(b) | keep order:false, desc:false | ++-------------------+---------+---------+------+-------------------------+------------------------------+ +1 row in set, 1 warning (0.00 sec) + +mysql> SHOW WARNINGS; ++-------+------+-------------------------------------------------------------------------------------------+ +| Level | Code | Message | ++-------+------+-------------------------------------------------------------------------------------------+ +| Note | 1105 | unique index idx_b of t is selected since the path only has point ranges with double scan | ++-------+------+-------------------------------------------------------------------------------------------+ +1 row in set (0.00 sec) +``` + +#### Skyline-Pruning + +Skyline-Pruning 是一个针对索引的启发式过滤规则,能降低错误估算导致选错索引的概率。Skyline-Pruning 从以下三个维度衡量一个索引的优劣: - 索引的列涵盖了多少访问条件。“访问条件”指的是可以转化为某列范围的 `where` 条件,如果某个索引的列集合涵盖的访问条件越多,那么它在这个维度上更优。 -对于这三种维度,如果某个索引 `idx_a` 在**三个维度上都不比 `idx_b` 差**,且**有一个维度比 `idx_b` 好**,那么就会优先选择 `idx_a`。 +- 选择该索引读表时,是否需要回表(即该索引生成的计划是 IndexReader 还是 IndexLookupReader)。不用回表的索引在这个维度上优于需要回表的索引。如果均需要回表,则比较索引的列涵盖了多少过滤条件。过滤条件指的是可以根据索引判断的 `where` 条件。如果某个索引的列集合涵盖的访问条件越多,则回表数量越少,那么它在这个维度上越优。 + ++ 选择该索引是否能满足一定的顺序。因为索引的读取可以保证某些列集合的顺序,所以满足查询要求顺序的索引在这个维度上优于不满足的索引。 + +对于以上三种维度,如果索引 `idx_a` 在这三个维度上都不比 `idx_b` 差,且有一个维度比 `idx_b` 好,那么 TiDB 会优先选择 `idx_a`。在执行 `EXPLAIN FORMAT = 'verbose' ...` 语句时,如果 Skyline-Pruning 排除了某些索引,TiDB 会输出一条 NOTE 级别的 warning 提示哪些索引在 Skyline-Pruning 排除之后保留下来。 + +在以下示例中,索引 `idx_b` 和 `idx_e` 均劣于 `idx_b_c`,因而被 Skyline-Pruning 排除,`SHOW WARNING` 的返回结果显示了经过 Skyline-Pruning 后剩余的索引。 + +```sql +mysql> CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, d INT, e INT, INDEX idx_b(b), INDEX idx_b_c(b, c), INDEX idx_e(e)); +Query OK, 0 rows affected (0.01 sec) + +mysql> EXPLAIN FORMAT = 'verbose' SELECT * FROM t WHERE b = 2 AND c > 4; ++-------------------------------+---------+---------+-----------+------------------------------+----------------------------------------------------+ +| id | estRows | estCost | task | access object | operator info | ++-------------------------------+---------+---------+-----------+------------------------------+----------------------------------------------------+ +| IndexLookUp_10 | 33.33 | 738.29 | root | | | +| ├─IndexRangeScan_8(Build) | 33.33 | 2370.00 | cop[tikv] | table:t, index:idx_b_c(b, c) | range:(2 4,2 +inf], keep order:false, stats:pseudo | +| └─TableRowIDScan_9(Probe) | 33.33 | 2370.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | ++-------------------------------+---------+---------+-----------+------------------------------+----------------------------------------------------+ +3 rows in set, 1 warning (0.00 sec) + +mysql> SHOW WARNINGS; ++-------+------+------------------------------------------------------------------------------------------+ +| Level | Code | Message | ++-------+------+------------------------------------------------------------------------------------------+ +| Note | 1105 | [t,idx_b_c] remain after pruning paths for t given Prop{SortItems: [], TaskTp: rootTask} | ++-------+------+------------------------------------------------------------------------------------------+ +1 row in set (0.00 sec) +``` ### 基于代价选择