最佳多列索引公式

2024-06-22 15:15:22 浏览数 (2)

x 个等值条件字段 1 个范围条件字段 or y 个排序字段 z 个其他需要获取的字段。(x >= 0, y >= 0, z >= 0)

例子:

代码语言:javascript复制
SELECT a, b, c, d, e FROM table WHERE a = 1 AND b = 2 ORDER BY c DESC, d ASC;

最佳索引为:

代码语言:javascript复制
(   a,  b,     c DESC, d ASC,         e        )
  等值条件字段 |   排序条件字段   | 其他需要获取的字段

等值条件字段

等值条件字段是指 WHERE 中使用 = 的字段。比如 WHERE class = 2 中的 class 就是等值条件字段。

范围条件字段

范围条件字段是指 WHERE 中使用 ><>=<= 等操作符的字段。比如 WHERE score > 90 中的 score 就是范围条件字段。

范围条件对于查询效率的影响非常大,所以应该尽量减少范围条件的使用。在最佳多列索引公式中,最多有一个范围条件字段,且不能和排序字段并存。如果有排序需求,应优先考虑排序,想办法规避范围条件筛选。下面通过一个例子来说明为什么范围条件字段不能和排序字段并存,以及如何规避范围条件筛选。

假设有一个表 films,包含了电影的信息,其中包含了 namerelease_dateratingcountry 等字段。现在要查询评分大于 8.0 的中国电影并按上映日期排序,SQL 语句如下:

代码语言:javascript复制
SELECT * FROM films WHERE rating > 8.0 AND country = 'China' ORDER BY release_date DESC;

当索引为 (country, rating, release_date) 时:

代码语言:javascript复制
index on (country, rating, release_date)

                                                          |
                                                          v ------------->
 --------------  --------- --------- --------- ------- ------- ------- ------- ------- ------- ------- 
| country      || America | America | America | China | China | China | China | Japan | Japan | Japan |
 --------------  --------- --------- --------- ------- ------- ------- ------- ------- ------- ------- 
| rating       ||   6.0   |   8.5   |   9.0   |  7.0  |  8.5  |  9.0  |  9.5  |  8.0  |  8.5  |  9.0  |
 --------------  --------- --------- --------- ------- ------- ------- ------- ------- ------- ------- 
| release_date ||  2024   |  2021   |  2023   | 2023  | 2015  | 2022  | 2016  | 2023  | 2013  | 2015  |
 --------------  --------- --------- --------- ------- ------- ------- ------- ------- ------- ------- 
                                                         ✅       ✅      ✅

看起来这个索引很完美,但实际上通过索引查找到的结果并不是按照 release_date 排序的,也就是说索引中的 release_date 是无效的。数据库还需要将所有符合条件的行按照 release_date 进行排序,如果数据量很大,这个排序操作会非常耗时。如果我们仅仅需要前几条数据,那么这个排序操作就是浪费的。这就是为什么范围条件字段不能和排序字段并存,且应优先考虑排序的原因。

这种场景的优化方法是将范围条件转换为等值条件。这需要根据实际需求来做优化。比如我们例子中的需求是查询评分大于 8.0 的电影,我们可以将评分大于 8.0 的电影定义为高分电影。增加一个字段 is_high_rating,当评分大于 8.0 时,is_high_rating 为 1,否则为 0。然后将索引改为 (country, is_high_rating, release_date)

代码语言:javascript复制
SELECT * FROM films WHERE is_high_rating = 1 AND country = 'China' ORDER BY release_date DESC;
代码语言:javascript复制
index on (country, is_high_rating, release_date)

                                                                            |
                                                            <-------------- v
 ----------------  --------- --------- --------- ------- ------- ------- ------- ------- ------- ------- 
| country        || America | America | America | China | China | China | China | Japan | Japan | Japan |
 ----------------  --------- --------- --------- ------- ------- ------- ------- ------- ------- ------- 
| is_high_rating ||    0    |    1    |    1    |   0   |   1   |   1   |   1   |   1   |   1   |   1   |
 ----------------  --------- --------- --------- ------- ------- ------- ------- ------- ------- ------- 
| release_date   ||  2024   |  2021   |  2023   | 2023  | 2015  | 2016  | 2022  | 2013  | 2015  | 2023  |
 ----------------  --------- --------- --------- ------- ------- ------- ------- ------- ------- ------- 
                                                           ✅       ✅      ✅

如果数据库支持,也可以使用函数索引 (country, IF(rating > 8, 1, 0), release_date),或者使用虚拟列来实现。

注意: !=<>IS NOT NULL 等不等操作符也是范围条件。如有需要,也可以通过转换为等值条件来优化。

排序字段

排序字段是指 ORDER BY 中的字段。比如 ORDER BY release_date DESC 中的 release_date 就是排序字段。

排序字段的顺序应该和 ORDER BY 中的顺序一致,且升降序也应该一致或完全相反。例如 ORDER BY release_date DESC, rating ASC 对应的索引应该是 (release_date DESC, rating ASC)(release_date ASC, rating DESC)。之所以完全相反是因为数据库可以倒序遍历索引。例如我们要将查询按照 release_date 升序、rating 降序排序的前五个结果:

代码语言:javascript复制
SELECT * FROM films ORDER BY release_date DESC, rating ASC LIMIT 5;

当索引为 (release_date DESC, rating ASC) 时:

代码语言:javascript复制
index on (release_date DESC, rating ASC)

                     |
                     v -------------------------------->
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
| release_date ||  2024   | 2023  | 2023  |  2023   | 2022  |  2021   | 2016  | 2015  | 2015  | 2013  |
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
| rating       ||   6.0   |  7.0  |  8.0  |   9.0   |  9.0  |   8.5   |  9.5  |  8.5  |  9.0  |  8.5  |
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
                     ✅       ✅      ✅       ✅        ✅

当索引为 (release_date ASC, rating DESC) 时:

代码语言:javascript复制
index on (release_date ASC, rating DESC)

                                                                                                  |
                                                                 <------------------------------- v
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
| release_date ||  2013   | 2015  | 2015  |  2016   | 2021  |  2022   | 2023  | 2023  | 2023  | 2024  |
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
| rating       ||   8.5   |  9.0  |  8.5  |   9.5   |  8.5  |   9.0   |  9.0  |  8.0  |  7.0  |  6.0  |
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
                                                                ✅        ✅      ✅      ✅       ✅

但是不能使用 (release_date DESC, rating DESC) 索引,因为这并不符合需求中的排序。

代码语言:javascript复制
index on (release_date DESC, rating DESC)

                     |
                     v -------------------------------->
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
| release_date ||  2024   | 2023  | 2023  |  2023   | 2022  |  2021   | 2016  | 2015  | 2015  | 2013  |
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
| rating       ||   6.0   |  9.0  |  8.0  |   7.0   |  9.0  |   8.5   |  9.5  |  9.0  |  8.5  |  8.5  |
 --------------  --------- ------- ------- --------- ------- --------- ------- ------- ------- ------- 
                     ✅       ✅      ✅       ✅        ✅

可以看出数据库使用 (release_date DESC, rating DESC) 索引获得的结果并不符合 ORDER BY release_date DESC, rating ASC 的排序要求,还需要进行额外的排序操作。

其他需要获取的字段(索引覆盖)

其他需要获取的字段指的是需要被 SELECT 且还不在索引中的字段。如果索引中包含了所有需要获取的字段,那么数据库可以直接从索引中获取数据,而不需要再去表中查询数据。这样可以减少 I/O 操作,提高查询效率。但是如果索引中包含了太多字段,会导致索引变得过大,从而影响到插入、更新、删除等操作的性能,也会增加不必要的内存占用。所以并不是直接把所有字段都放到索引中就是最佳的,需要根据实际情况来做权衡。

0 人点赞