第3章 TiDB的运算(三)


SQL层简介

TiDB的SQL层,即tidb-server,给Google的F1比较类似,负责将SQL翻译成Key-Value操作,将其转发给共用的分布式Key-Value存储层TiKV,然后组装TiKV返回的结果,最终将查询结果返回给客户端。

这一层的节点都是无状态的,节点本身并不存储数据,节点之间完全对等。

1、SQL运算
能想到的最简单的方案就是通过上一节所述的表中所有数据和Key-Value的映射关系映射方案,将SQL查询映射为对KV的查询,再通过KV接口获取对应的数据,最后执行各种计算。
比如select count(*) from user where name = "TiDB"这样一个语句,我们需要读取表中所有的数据,然后检查name字段是否是TiDB,如果是的话,则返回这一行。具体流程是:

1)构造出Key Range:一个表中所有的RowID都在[0, MaxInt64)这个范围内,那么我们用0和MaxInt64根据行数据的Key编码规则,就能构造出一个[StartKey, EndKey)的左闭右开区间。
2)扫描Key Range:根据上面构造出的Key Range,读取TiKV中的数据。
3)过滤数据:对于读到的每一行数据,计算name = "TiDB"这个表达式,如果为真,则向上返回这一行,否则丢弃这一行数据。
4)计算count(*):对符合要求的每一行,累计到count(*)的结果上面

整个流程示意图如下:

这个方案肯定是可以Work的,但是并不能Work的很好,原因是显而易见的:

1)在扫描数据的时候,每一行都要通过KV操作从TiKV中读取出来,至少有一次RPC开销,如果需要扫描的数据很多,那么这个开销会非常大。
2)并不是所有的行都有用,如果不满足条件,其实可以不读取出来。
3)符合要求的行的值并没有什么意义,实际上这里只需要有几行数据这个信息就行。

2、分布式SQL运算
如何避免上述缺陷也是显而易见的,我们需要将计算尽量靠近存储节点,以避免大量的RPC调用。
我们需要将SQL中的谓词条件下推到存储节点进行计算,这样只需要返回有效的行,避免无意义的网络传输。然后,我们还可以将聚合函数Count(*)也下推到存储节点,进行预聚合,每个节点只需要返回一个Count(*)的结果即可,再由SQL曾将各个节点返回的Count(*)的结果累加求和。
这里有一个数据逐层返回的示意图:

3、SQL层架构
通过上面的例子,希望大家对SQL语句的处理有一个基本的了解。实际上TiDB的SQL层要复杂的多,模块以及层次非常多,下面这个图列出了重要的模块以及调用关系:

用户的SQL请求会直接或者通过Load Balancer发送到tidb-server,tidb-server会解析MySQL Protocol Packet,获取请求内容,对SQL进行语法解析和语义分析,制定和优化查询计划,执行查询计划并获取和处理数据。数据全部存储在TiKV集群中,所以在这个过程中tidb-server需要和TiKV交互,获取数据。最后tidb-server需要将查询结果返回给用户。