3.3 SQL 层简介
TiDB 的 SQL层,即 tidb-server,跟 Google 的 F1 比较类似,负责将 SQL 翻译成 Key-Value 操作,将其转发给共用的分布式 Key-Value 存储层 TiKV,然后组装 TiKV 返回的结果,最终将查询结果返回给客户端。
这一层的节点都是无状态的,节点本身并不存储数据,节点之间完全对等。
3.3.1 SQL 运算
能想到的最简单的方案就是通过上一节所述的 表中所有数据和 Key-Value 的映射关系 映射方案,将 SQL 查询映射为对 KV 的查询,再通过 KV 接口获取对应的数据,最后执行各种计算。
比如 select count(*) from user where name = "TiDB"
这样一个语句,我们需要读取表中所有的数据,然后检查 name
字段是否是 TiDB
,如果是的话,则返回这一行。具体流程是:
- 构造出 Key Range:一个表中所有的
RowID
都在[0, MaxInt64)
这个范围内,那么我们用0
和MaxInt64
根据行数据的Key
编码规则,就能构造出一个[StartKey, EndKey)
的左闭右开区间 - 扫描 Key Range:根据上面构造出的 Key Range,读取 TiKV 中的数据
- 过滤数据:对于读到的每一行数据,计算
name = "TiDB"
这个表达式,如果为真,则向上返回这一行,否则丢弃这一行数据 - 计算
Count(*)
:对符合要求的每一行,累计到Count(*)
的结果上面
整个流程示意图如下:
这个方案肯定是可以 Work 的,但是并不能 Work 的很好,原因是显而易见的:
- 在扫描数据的时候,每一行都要通过 KV 操作从 TiKV 中读取出来,至少有一次 RPC 开销,如果需要扫描的数据很多,那么这个开销会非常大
- 并不是所有的行都有用,如果不满足条件,其实可以不读取出来
- 符合要求的行的值并没有什么意义,实际上这里只需要有几行数据这个信息就行
3.3.2 分布式 SQL 运算
如何避免上述缺陷也是显而易见的,我们需要将计算尽量靠近存储节点,以避免大量的 RPC 调用。首先,我们需要将 SQL 中的谓词条件下推到存储节点进行计算,这样只需要返回有效的行,避免无意义的网络传输。然后,我们还可以将聚合函数 Count(*)
也下推到存储节点,进行预聚合,每个节点只需要返回一个 Count(*)
的结果即可,再由 SQL 层将各个节点返回的 Count(*)
的结果累加求和。
这里有一个数据逐层返回的示意图:
3.3.3 SQL 层架构
通过上面的例子,希望大家对 SQL 语句的处理有一个基本的了解。实际上 TiDB 的 SQL 层要复杂的多,模块以及层次非常多,下面这个图列出了重要的模块以及调用关系:
用户的 SQL 请求会直接或者通过 Load Balancer
发送到 tidb-server,tidb-server 会解析 MySQL Protocol Packet
,获取请求内容,对 SQL 进行语法解析和语义分析,制定和优化查询计划,执行查询计划并获取和处理数据。数据全部存储在 TiKV 集群中,所以在这个过程中 tidb-server 需要和 TiKV 交互,获取数据。最后 tidb-server 需要将查询结果返回给用户。