TiDB原理解析系列(二)---SQL到KV模型的存储与查询映射

考虑做一个存储系统,首先要考虑的就是数据的存储模型。TiKV选择的是Key-Value模型,并且这个模型提供有序遍历的方法。简单来讲,TiKV就是一个巨大的Map,其中KeyValue都是原始的Byte数组,在这个Map中,Key按照Byte数组二进制比特位比较排列。

  • TiKV是一个巨大的Map,存储的是Key-Value Pair
  • TiKV Map中的Key-Value pair按照Key的二进制顺序有序。可以Seek到某一个Key的位置,然后不断的调用Next方法,以递增的顺序获取比这个Key大的Key-Value

实现上,TiKV没有直接向磁盘上写数据,而是把数据写入RocksDB中,具体的数据落地由RocksDB负责。可以简单的认为,TiKV是一个分布式Key-Value MapRocksDB是一个单机的Key-Value Map

有了这个存储模型,接下来就需要考虑如何在KV模型上保存关系型数据以及如何在KV上运行SQL语句。

一、SQLKV模型的存储

创建一个表结构:

CREATE TABLE User {
        ID int,
        Age int,
        Name varchar(20),
        Gender varchar(20),
        PRIMARY KEY (ID),
        UNIQUE index idxName (Name),  //唯一索引
        index idxGender (Gender)
};

接着插入数据:


image.png

那么到底需要存储哪些数据,做哪些映射?为了方便理解,我们拿SQLB+树模型(MySQL默认数据引擎InnoDB数据存储模型)来做说明。

1.表数据以及主索引存储

InnoDB中,表数据本身就是按B+树组织的一个索引结构,B+树叶节点的data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据本身就是主索引。表数据以及主索引实际存储如下图:

image.png

TiDB也采用了这个方式,即表数据本身也就是主索引。data域即value:[col1,col2,col3,col4]保存完整的数据记录,这里的难点是key的设计。
TiKV使用了RocksDBColumn Families(CF)特性,默认 RocksDB 实例将 KV 数据存储在内部的 default、write 和 lock 3 个 CF 内

  • default CF存储的是真正的数据,与其对应的参数位于[rocksdb.defaultcf]项中;
  • write CF存储的是数据的版本信息 (MVCC)以及索引相关的数据,相关的参数位于[rocksdb.writecf]项中;
  • lock CF存储的是锁信息,系统使用默认参数。

表数据文件数据存储在rocksdb.defaultcf中,索引存储在rocksdb.writecf。所以KEY中需要包含识别Database/TabletableID,识别的行的rowID(Primary Key),识别索引的indexID。每行数据按照规则进行编码成KV pair:

Key: tablePrefix{tableID}_recordPrefixSep{rowID}
Value: [col1,col2,col3,col4]

注:tablePrefix/recordPrefixSep 都是特定的字符串常量,用于区分其他数据。

var(
    tablePrefix     = []byte{"_t"}
    recordPrefixSep = []byte("_r")
    indexPrefixSep  = []byte("_i")
)

假设TiDB分配的tableId为10,表数据以及主索引实际存储如下:

t10_r15--->[34,Bob,Female]
t10_r18--->[77,Alice,Male]
t10_r20--->[5,Jim,Female]
t10_r30--->[91,Eric,Female]
t10_r49--->[22,Tom,Male]
t10_r50--->[89,Rose,Male]

2.辅助索引存储

InnoDB中除了主索引以外还有辅助索引。InnoDB的辅助索引data域存储相应记录主键的值,而不是完整的数据记录。主索引key是唯一的,而辅助索引的key可以重复。但是B+树要求键的值必须唯一,所以这里把辅助键的值和主键的值合并起来作为在B+树中的真正键值,保证了唯一性。辅助索引存储如下图:

image.png

TiDBInnoDB辅助索引的设计基本保持一致,但是区分了重复辅助索引和唯一辅助索引,并且为每个辅助索引分配了一个indexID
唯一辅助索引:

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue
Value: rowID

假设TiDB分配的tableID为10,indexID为1。Unique索引indexName实际存储为:

t10_r1_Bob--->15
t10_r1_Alice--->18
......

重复辅助索引:

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID
Value: null

假设TiDB分配的tableId为10,indexID为2。索引idxGender实际存储为:

t10_r2_Fmale_15--->null
t10_r2_Male_18--->null
......

3.表结构存储

每个Database/Table都被分配了一个唯一的ID,这个ID作为唯一标识,并且在编码为Key-Value时,这个ID会编码到Key中,加上m前缀。这样可以构造出一个KeyValue中存储的是序列化后的元信息。一对KV pair就可以表示一个表结构信息:

Key: tableMetaPrefix{tableID}
value:[TableName,cloName,cloName,colName,colName]

假设TiDB分配的tableId为10,实际表结构存储为:

m10--->[User,ID,Age,Name,Gender]

二 、SQLkv模型的查询映射

前面提到,将TiKV看做一个巨大的有序的KV Map,那么为了实现存储的水平扩展,需要将数据分散在多台机器上。对于一个KV系统,将数据分散在多台机器上有两种比较典型的方案:一种是按照KeyHash,根据Hash值选择对应的存储节点;另一种是分Range,某一段连续的Key都保存在一个存储节点上。TiKV选择了第二种方式,将整个Key-Value空间分成很多段,每一段是一系列连续的Key,我们将每一段叫做一个Region,并且会尽量保持每个Region中保存的数据不超过一定的大小(这个大小可以配置,目前默认是64mb)。每一个Region都可以用StartKeyEndKey这样一个左闭右开区间来描述。也就是说上面的数据在实际存储中,按Region划分,是存储在不同的TiKVNode中。如下图所示:

image.png

当对这个表执行一条SQL语句Select count(*) from User where id > 0 and id < 90时,数据不再分布在一台物理机上,而是分布在3个物理机上。分布式SQL操作如下:

  1. 构造Key Range区间(主键ID划分)。区间:[0,20),[20,40),[40,60)…
  2. 获取key Range所在Tikv节点。
  3. 优化执行计划。即将where条件与count(*),一起推到相应的Tikv节点。
  4. 每个Tikv节点过滤数据,计算count(*)
  5. TiDB Server将每个Tikv节点计算count(*),合并起来计算。
    image.png

以上就是一个分布式的SQL计算,很明显它是不同于MySQLSQL查询。不管是NewSQL数据库还是传统数据库,都会对 optimizer进行优化。上面对count(*)的优化设计,一方面增加了计算并行度,同时也减少了网络交互。TiDB在优化方面做了很多工作,比如常量折叠、常量传播、JOIN选择等,并且有一个框架去统计下层数据信息,在此基础上继续做优化。具体优化可以参考MPP and SMP in TiDB

三、TiDB SQL层框架

上面主要介绍了SQLKV存储映射以及SQL分布式计算相关功能,让我们从SQL的角度去了解数据是如何存储,SQL是如何计算执行的。这些都是在SQL层框架中实现的。实际TiDBSQL层非常复杂,涉及到很多优化器分布原理,分布框架执行的细节,是TiDB中比较复杂的模块。下图列出了重要的模块以及调用关系(部分来图来自网络):

image.png

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354