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