TiDB 源码阅读系列文章(十三)索引范围计算简介

2024-04-08 03:18

本文主要是介绍TiDB 源码阅读系列文章(十三)索引范围计算简介,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简述

在数据库中处理查询请求时,如果可以尽早的将无关数据过滤掉,那么后续的算子就可以少做无用功,提升整个 SQL 的执行效率。过滤数据最常用的手段是使用索引,TiDB 的优化器也会尽量采用索引过滤的方式处理请求,利用索引有序的特点来提升查询效率。比如当查询条件为 a = 1 时,如果 a 这一列上有索引,我们就可以利用索引很快的把满足 a = 1 的数据拿出来,而不需要逐行检查 a 的值是否为 1。当然是否会选择索引过滤也取决于代价估算。

索引分为单列索引和多列索引(组合索引),筛选条件也往往不会是简单的一个等值条件,可能是非常复杂的条件组合。TiDB 是如何分析这些复杂条件,来得到这些条件在对应的索引上的逻辑区间范围(range),就是本文要介绍的内容。

关于 TiDB 如何构建索引,如何存储索引数据,希望读者能够有基本的了解(参考阅读:三篇文章了解 TiDB 技术内幕 - 说计算 )。

这里是一个例子,展示这里所说的索引范围计算是做什么的,建表语句和查询语句如下:

CREATE TABLE t (a int primary key, b int, c int);
select * from t where ((a > 1 and a < 5 and b > 2) or (a > 8 and a < 10 and c > 3)) and d = 5;

计算索引逻辑区间范围的流程如下:

流程图

从上图可以看出,整个流程分为从 Filter 中抽取可用索引的表达式以及利用选出的表达式构造数据范围两个步骤,接下来分别描述。

抽取表达式

这个步骤是从 Filter 中将能够用上索引的表达式选出来。由于单列索引和多列索引在处理逻辑上有很大的不同,所以会分单列索引和多列索引两中情况进行讲解。

单列索引

单列索引的情况相对来说比较简单。很多满足 Column op Constant 形式的简单表达式都可以用来计算 range,单个表达式的判断逻辑在 checker.go 的 conditionChecker 中。而对于包含了 AND 或者 OR 的复杂情况,我们可以按照下述规则进行处理:

  1. AND 表达式无关的 filter 并不会影响其可以计算 range 的子项。所以直接舍去无关的表示即可。以流程图中的一个子表达式 a > 1 and a < 5 and b > 2 为例,我们只要将 b > 2 扔掉,保留 a > 1 and a < 5 即可。

  2. OR 表达式中,每个子项都要可以用来计算 range,如果有不可以计算 range 的子项,那么这整个表达式都不可用来计算 range。以 a = 1 or b = 2 为例,b = 2 这一子项不可以用来计算 a 的 range,所以这个表达式整体上无法计算 a 的 range。而如果是 a > 1 or ( a < 2 and b = 1),根据 1 中的规则,第二个子项会留下 a < 2 的部分,可以用来计算 a 的 range,因此整个表达式会返回 a > 1 and a < 2 来供接下来计算 range 的部分处理。

这里补充说明一点,TiDB 的主键在实现方式上限定了只有整数类型的单列主键会把主键值当做 RowID,然后编码成 RowKey,和这行数据存储在一起。其他类型的单列主键会作为普通的 unique key 看待,当查询的列包含索引上没有的列时,需要一次查索引 + 一次扫表。所以我们将这种整数类型作为主键的索引处理逻辑单独抽取出来,其入口函数为 DetachCondsForTableRange 。其中对 AND 表达式和 OR 表达式的处理入口分别为 detachColumnCNFConditions 和 detachColumnDNFConditions。这两个函数也用来处理其他类型的主键或者索引的的 range 计算。

多列索引

多列索引的情况较单列索引而言会复杂一些,因为在处理 OR 表达式中列与列之间的关系需要考虑更多情况。TiDB 中为了简化 ranger 的逻辑,目前只考虑下列情况:

  1. AND 表达式中,只有当之前的列均为点查的情况下,才会考虑下一个列。

    e.g. 对于索引 (a, b, c),有条件 a > 1 and b = 1,那么会被选中的只有 a > 1。对于条件 a in (1, 2, 3) and b > 1,两个条件均会被选到用来计算 range。

    由于非点查的部分只会涉及到一个列,所以可以直接复用 detachColumnCNFConditions

  2. OR 表达式中,每个子项会视为 AND 表达式分开考虑。与单列索引的情况一样,如果其中一个子项无法用来计算索引,那么该 OR 表达式便完全无法计算索引。

多列索引处理的入口函数为 DetachCondAndBuildRangeForIndex,AND 表达式和 OR 表达式的处理入口分别为 detachCNFCondAndBuildRangeForIndex 和 detachDNFCondAndBuildRangeForIndex。(由于多列索引对 range 的处理相对单列索引而言会复杂一些,所以没有拆分为 DetachCondition 和 BuildRange 两部分,而是由 DetachCondAndBuildRangeForIndex 处理。)

由于逻辑进行到了简化,因此目前 TiDB 的 ranger 存在无法正确处理的情况。比如:

  • a = 1 and (b = 1 or b = 2) and c > 1。对于这个式子,当 (a, b ,c) 为索引时,如上述所言,由于 (b = 1 or b = 2) 形式上是 OR 表达式的情况,而非点查。所以会在 b 列停止,不会考虑 c > 1 的情况。所以目前为了兼容 TiDB 的逻辑,遇到这种情况尽量改写为 a = 1 and b in (1, 2) and c > 1 的形式。

  • 类似的如 ((a = 1 and b = 1) or (a = 2 and b = 2)) and c = 1 形式的式子,前段 OR 表达式实际上为点查的行为,但是由于是 OR 连接起来的式子,所以在 TiDB 的逻辑中作为范围查询处理,因此 c = 1 不会作为索引的计算条件处理。而这时改写为 (a, b) in ((1, 1),  (2, 2)) and c = 1 的形式也不会使 c = 1 选入索引计算的条件,原因是多列 in 的函数会被 TiDB 改写为 OR 连接的形式,所以 ((a = 1 and b = 1) or (a = 2 and b = 2)) 和 (a, b) in ((1, 1),  (2, 2)) 在 TiDB 中是完全一致的行为。针对这种情况,目前的办法只有将这些条件都放入 OR 的子项中,针对这里用到的例子,那就是要改写为 ((a = 1 and b = 1 and c = 1) or (a = 2 and b = 2 and c = 1))

计算逻辑区间

这一步骤中,利用上一步抽取出来的表达式估算出数据的逻辑区间范围,后续会根据这个逻辑区间以及数据编码方式构造物理区间进行数据访问。我们仍然分为单列索引和多列索引两个情况来介绍。

单列索引

这种情况下,输入的表达式为 Column op Constant 形式的简单表达式由 OR 以及 AND 连接而成。我们对每一个具体的操作符,都设计了一段对应的计算 range 的逻辑,当遇到 AND 或者 OR 时,会对两个区间求交或者求并。在 point.go 中有一个 builder 的结构体用来处理上述逻辑。

在这个阶段我们记录 range 时用 rangePoint 的结构来存储 range。

// Point is the end point of range interval.
type point struct {value types.Datumexcl  bool // excludestart bool
}

每个 point 代表区间的一个端点,其中的 excl 表示端点为开区间的端点还是闭区间的端点。start 表示这个端点是左端点还是右端点。

builder 中每个 buildFromXXX 的方法都是计算一个具体函数的 range 的方法。比如 buildFromIn 便是处理 in 函数 的方法。可以看到它首先对 in 函数的值列表的每个值都构造了一个 rangPoint 的单点区间,然后对这些区间放在一个 slice 中做排序以及去重。最终将去重后的结果输出。

在 pointer.go 中还包含其他各类的函数的处理,具体可以翻阅源代码。

除了对具体函数的处理外,pointer.go 中还有区间交和区间并的操作。intersection 和 union 分别代表区间交和区间并。两个函数的逻辑均通过 merge 方法进行处理,通过传入一个 flag 来区分。merge 函数做了如下两个假设:

  • a, b 两个区间均已经做过去重

  • 单个区间序列内部不会有重叠的部分

merge 函数使用 inRangeCount 来记录当前位置被 a, b 两个区间序列覆盖的情况。区间求并的情况时,只要 a, b 两个区间序列中有一个区间序列覆盖便可以作为解输出,被两个区间同时覆盖的端点必然是属于一个更大的区间的内部不需要输出。所以当 inRangeCount 为 1 时,即为需要输出的区间端点。

当区间求交时,需要两个序列都覆盖到才是可以输出的端点,所以当 inRangeCount 为 2 时,即为需要输出的区间端点。

在得到最后的区间端点序列后,由 points2TableRanges 转化为对外暴露的 range 结构,由 BuildTableRange 输出到 plan package。

// NewRange represents a range generated in physical plan building phase.
type NewRange struct {LowVal  []types.DatumHighVal []types.DatumLowExclude  bool // Low value is exclusive.HighExclude bool // High value is exclusive.
}

在现在的 TiDB 中,单列索引和多列索引使用了相同的 range 结构,所以这里的端点值为 slice 的形式。

多列索引

对于多列索引,当其为 AND 表达式时,根据前述我们可以知道,其形式必为索引前缀列上的等值条件再加上关于前缀之后一个列的复杂条件组成。所以我们只需要按顺序处理点查的等值条件部分,将点查的区间依次 append 到 NewRange 中的 LowVal 和 HighVal 两个 Slice 中即可(appendPoints2Ranges)。处理到最后一列时,将之前的 NewRange 按最后非点查列所计算得到的区间个数拷贝一下,再依次 append 即可。具体代码可见 buildCNFIndexRange。

对于 OR 表达式的情况,由于此时 range 已经无法转回 point 的结构。所以这里重新实现了一下区间并的操作。实现的形式便是比较常见的将区间按左端点排序,在依次扫过区间的同时,记录当前所有重叠过的区间的最右右端点来进行做区间并的算法。区间并的具体的实现可见 unionRanges 方法。

Future Plan

  • 目前 TiDB 对单列索引的处理在逻辑上已经非常完备,在实际表现上可能由于没有对部分函数实现计算 range 的逻辑而有遗漏。这部分会根据情况进行优化。

  • 如上文提到的,目前 TiDB 为了简化 ranger 的逻辑,对多列索引做了一些假设。未来会尝试去掉或者弱化这些假设,或者在前期对 SQL 进行更充分的改写使得 SQL 不会触发这些假设,来提供更加强大的功能,免于手动 rewrite 的麻烦。

  • 目前 TiDB 对简单式子的形式的检查限定在了 Column op Constant 的形式。所以诸如 from_unixtime(timestamp_col) op datetime_constant 形式的条件是无法计算索引的,也需要手动 rewrite 为 timestamp_col op timestamp_constant 才可以使用到索引。这部分也会考虑进行改进以提升用户体验。

作者:崔一丁

这篇关于TiDB 源码阅读系列文章(十三)索引范围计算简介的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/884424

相关文章

轻量级在线服装3D定制引擎Myway简介

我写的面向web元宇宙轻量级系列引擎中的另外一个,在线3D定制引擎Myway 3D。 用于在线商品定制,比如个性化服装的定制、日常用品(如杯子)、家装(被套)等物品的在线定制。 特性列表: 可更换衣服款式,按需定制更换模型可实时更改材质颜色可实时添加文本,并可实时修改大小、颜色和角度,支持自定义字体可实时添加艺术图标,并可实时修改大小、颜色和角度,支持翻转、各种对齐可更改衣服图案,按需求定制

计算绕原点旋转某角度后的点的坐标

问题: A点(x, y)按顺时针旋转 theta 角度后点的坐标为A1点(x1,y1)  ,求x1 y1坐标用(x,y)和 theta 来表示 方法一: 设 OA 向量和x轴的角度为 alpha , 那么顺时针转过 theta后 ,OA1 向量和x轴的角度为 (alpha - theta) 。 使用圆的参数方程来表示点坐标。A的坐标可以表示为: \[\left\{ {\begin{ar

mysql索引四(组合索引)

单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引;组合索引,即一个索引包含多个列。 因为有事,下面内容全部转自:https://www.cnblogs.com/farmer-cabbage/p/5793589.html 为了形象地对比单列索引和组合索引,为表添加多个字段:    CREATE TABLE mytable( ID INT NOT NULL, use

mysql索引三(全文索引)

前面分别介绍了mysql索引一(普通索引)、mysql索引二(唯一索引)。 本文学习mysql全文索引。 全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用【分词技术】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。 在MySql中,创建全文索引相对比较简单。例如:我们有一个文章表(article),其中有主键ID(

mysql索引二(唯一索引)

前文中介绍了MySQL中普通索引用法,和没有索引的区别。mysql索引一(普通索引) 下面学习一下唯一索引。 创建唯一索引的目的不是为了提高访问速度,而只是为了避免数据出现重复。唯一索引可以有多个但索引列的值必须唯一,索引列的值允许有空值。如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该使用关键字UNIQUE,把它定义为一个唯一索引。 添加数据库唯一索引的几种

mysql索引一(普通索引)

mysql的索引分为两大类,聚簇索引、非聚簇索引。聚簇索引是按照数据存放的物理位置为顺序的,而非聚簇索引则不同。聚簇索引能够提高多行检索的速度、非聚簇索引则对单行检索的速度很快。         在这两大类的索引类型下,还可以降索引分为4个小类型:         1,普通索引:最基本的索引,没有任何限制,是我们经常使用到的索引。         2,唯一索引:与普通索引

springboot家政服务管理平台 LW +PPT+源码+讲解

3系统的可行性研究及需求分析 3.1可行性研究 3.1.1技术可行性分析 经过大学四年的学习,已经掌握了JAVA、Mysql数据库等方面的编程技巧和方法,对于这些技术该有的软硬件配置也是齐全的,能够满足开发的需要。 本家政服务管理平台采用的是Mysql作为数据库,可以绝对地保证用户数据的安全;可以与Mysql数据库进行无缝连接。 所以,家政服务管理平台在技术上是可以实施的。 3.1

JavaWeb系列二十: jQuery的DOM操作 下

jQuery的DOM操作 CSS-DOM操作多选框案例页面加载完毕触发方法作业布置jQuery获取选中复选框的值jQuery控制checkbox被选中jQuery控制(全选/全不选/反选)jQuery动态添加删除用户 CSS-DOM操作 获取和设置元素的样式属性: css()获取和设置元素透明度: opacity属性获取和设置元素高度, 宽度: height(), widt

高仿精仿愤怒的小鸟android版游戏源码

这是一款很完美的高仿精仿愤怒的小鸟android版游戏源码,大家可以研究一下吧、 为了报复偷走鸟蛋的肥猪们,鸟儿以自己的身体为武器,仿佛炮弹一样去攻击肥猪们的堡垒。游戏是十分卡通的2D画面,看着愤怒的红色小鸟,奋不顾身的往绿色的肥猪的堡垒砸去,那种奇妙的感觉还真是令人感到很欢乐。而游戏的配乐同样充满了欢乐的感觉,轻松的节奏,欢快的风格。 源码下载

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述

气象站的种类和应用范围可以根据不同的分类标准进行详细的划分和描述。以下是从不同角度对气象站的种类和应用范围的介绍: 一、气象站的种类 根据用途和安装环境分类: 农业气象站:专为农业生产服务,监测土壤温度、湿度等参数,为农业生产提供科学依据。交通气象站:用于公路、铁路、机场等交通场所的气象监测,提供实时气象数据以支持交通运营和调度。林业气象站:监测林区风速、湿度、温度等气象要素,为林区保护和