TIDB-存储和查询原理入门

TIDB 入门

前言

tidb 的数据存储节点主要是 tikv,下文我们先从tikv的基础知识开始,然后延申到tidb的兼容mysql查询式存储。

底层存储

存储模型 key-value 键值对

tikv 采用键值对模型进行存储,类似java中的map,但是tikv的key是支持二进制有序遍历的。

持久化存储 (基于RocksDB) FaceBook开源

TiKV 的本地化存储是基于 RocksDB 的,没有直接进行落盘,而是将需要落盘的数据交给了 RocksDB 进行处理。

高可用多副本存储 Raft协议

tikv作为tidb的数据存储支撑,如何保证数据安全,tikv选择了多副本存储方案,存储到多个机器上,单机出问题依然能提供服务。同时采用了Raft算法保证数据一致性。

Raft协议简介

Raft协议是负责多个tikv实例之前进行数据同步的。每个数据变更都会生成一条Raft日志,然后通过Raft的日志复制功能将数据可靠的同步到对应的节点中,不过在实际写入中,根据 Raft 的协议,只需要同步复制到多数节点,即可安全地认为数据写入成功。

Raft中的主要角色

  • Leader 主节点

  • Follower 副本(可多个)

    Raft协议在tikv的主要工作内容

  • 主节点选举

  • 成员变更(增删副本、Leader迁移)

  • 日志复制

来一张简单的tikv存储示意图

Region 副本块

TIKV 可以看为是一个大Map,但是为了实现数据的水平拓展则需要将数据分散到多个机器中。TIKV 不同于 java 中的 Map采用的是 Hash桶的形式确定key位置,而是采用了按照key划分Range,某一段key保存在一个节点上,这个也呼应了tikv支持二进制有序遍历。

Region切分,将数据分散在集群中多个节点上,保证数据高可用同时也可实现负载均衡。

基于Region为单元做数据复制,会保存多个副本,每个副本叫做Replica。Replica之间是通过Raft协议来保证数据的一致性,一个 Region 的多个 Replica 会保存在不同的节点上,构成一个 Raft Group。

TiFlash

TiFlash 是 TiDB HTAP 形态的关键组件,它是 TiKV 的列存扩展。不阻塞 TiKV 写入,通过 Raft 协议同步保证和tikv数据一致。

贴一张整体架构图

TIFlash 同步默认不开启,如需开启需要执行 ALTER TABLE {TABLE_NAME} SET TIFLASH REPLICA {REPLICA_SIZE}

kv数据库 映射为 关系型数据库

MVCC

为了提高并发读写带来的性能影响,tikv也实现了多版本并发控制。tikv的实现方式是在key上增加version,版本号越大的数据tikv会排在前面。

表数据与 TIDB Key-Value 的映射关系

在关系型数据库中一个表会有多个列,tidb将表中的单行数据映射为一个k-v键值对,尤其是在对key的构造上做了大量考虑。由于OLTP场景下针对单行多行的增删改查比较频繁所以映射到tikv上依然是有唯一id作为数据key的,大致key-value设计如下:

  • 基于表的概念tidb会生成一个 TableId (整数),集群内唯一。
  • 基于行的概念tidb会生成 RowId (整数),表内唯一,如果表里有整数型主键时,tidb会默认使用主键作为当前行 id,否则会隐式生成一个 RowId。

假设这是mysql数据表

id name age
1 张三 22
2 李四 24

映射到tidb则为

key value
tablePrefix{TableID}_recordPrefixSep{1} [1,张三,22]
tablePrefix{TableID}_recordPrefixSep{2} [2,李四,24]

其中 tablePrefix 和 recordPrefixSep 都是特定的字符串常量,用于在 Key 空间内区分其他数据。

索引数据与 TIDB Key-Value 的映射关系

为了兼容mysql.TIDB 同时也支持了主键和二级索引(唯一和非唯一都支持),和表数据映射方案类似 TiDB 为表中每个索引分配了一个索引 ID,用 IndexID 表示 。

假设这是mysql数据表

id name age id_card
1 张三 22 11011
2 李四 24 12033

表中有两个索引

  1. mysql的主键索引
  2. name和age字段的联合索引
  3. id_card 列的唯一索引

tidb 默认创建的原数据列便支持了主键索引。

如果是联合索引

key value
tablePrefix{TableID}_indexPrefixSep{IndexID}_indexedColumnsValue_1 null
tablePrefix{TableID}_indexPrefixSep{IndexID}_indexedColumnsValue_2 null

由于tikv底层支持有序遍历,所以如果要获取一段连续数据便可以直接遍历拿到。

如果是唯一索引

key value
tablePrefix{TableID}_indexPrefixSep{IndexID}_11011 1
tablePrefix{TableID}_indexPrefixSep{IndexID}_12033 2

小结

上面key的编码规则中 tablePrefix、recordPrefixSep 和 indexPrefixSep 都是字符串常量,用于在 Key 空间内区分其他数据。

上述方案中,无论是表数据还是索引数据的 Key 编码方案,一个表内所有的行都有相同的 Key 前缀,一个索引的所有数据也都有相同的前缀。这样具有相同的前缀的数据,在 TiKV 的 Key 空间内,是排列在一起的。因此只要小心地设计后缀部分的编码方案,保证编码前和编码后的比较关系不变,就可以将表数据或者索引数据有序地保存在 TiKV 中。采用这种编码后,一个表的所有行数据会按照 RowID 顺序地排列在 TiKV 的 Key 空间中,某一个索引的数据也会按照索引数据的具体的值(编码方案中的 indexedColumnsValue)顺序地排列在 Key 空间内。

元信息管理

TIDB 中的 DB 和 table 元数据信息也会兼容mysql的Schema,也是存储在tikv中的。

每个db和table都会有一个唯一id,这个id作为唯一标识存储在tikv中时会编码到key中,value则是存储的元信息。

sql处理层及查询流程

tidb 的sql层由TIDB Server 负责,负责客户端的连接、sql的解析、拆分成 key-value 数据库命令去请求对应的 TIKV,然后组装结果返回给客户端。

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

假如要查询一条sql语句 select count( * ) from user where name='TIDB' 具体查询流程如下

  1. TIKV Server 构造keyRange;当查询表中的数据时,数据一定的在 [0,maxRowId) 之间,根据key的编码规则便能决定一个 [StartKey, EndKey)的左闭右开区间。
  2. 查询 PD 找到 KeyRange 对应的 Regon 和 tikv 实例并请求数据。
  3. tikv遍历数据将符合条件的数据返回给 server 层。
  4. server层汇集数据将结果返回给客户端。

来一张 SQL 层架构图

以上就是 TIDB 的基本存储及兼容关系型数据库的逻辑概念,对以上有问题的可以翻阅官方文档。 https://docs.pingcap.com/zh/tidb