TiDB 2.0 中,我们引入了一个叫 Chunk 的数据结构用来在内存中存储内部数据,用于减小内存分配开销、降低内存占用以及实现内存使用量统计/控制,其特点如下:
Chunk 本质上是 Column 的集合,它负责连续的在内存中存储同一列的数据,接下来我们看看 Column 的实现。
Column 的实现参考了 Apache Arrow,Column 的代码在 这里。根据所存储的数据类型,我们有两种 Column:
Double
、Bigint
、Decimal
等Char
、Varchar
等哪些数据类型用定长 Column,哪些数据类型用变长 Column 可以看函数 addColumnByFieldType 。
Column 里面的字段非常多,这里先简单介绍一下:
用来表示这个 Column 有多少行数据。
用来表示这个 Column 中有多少 NULL
数据。
用来存储这个 Column 中每个元素是否是 NULL
,需要特殊注意的是我们使用 0 表示 NULL
,1 表示非 NULL
,和 Apache Arrow 一样。
存储具体的数据,不管定长还是变长的 Column,所有的数据都存储在这个 byte slice 中。
给变长的 Column 使用,存储每个数据在 data 这个 slice 中的偏移量。
给定长的 Column 使用,当需要读或者写一个数据的时候,使用它来辅助 encode 和 decode。
追加一个元素需要根据具体的数据类型调用具体的 append 方法,比如: appendInt64、appendString 等。
一个定长类型的 Column 可以用如下图表示:
我们以 appendInt64 为例来看看如何追加一个定长类型的数据:
unsafe.Pointer
把要 append 的数据先复制到 elemBuf 中;上面第 1 步在 appendInt64
这个函数中完成,第 2、3 步在 finishAppendFixed 这个函数中完成。其他定长类型元素的追加操作非常相似,感兴趣的同学可以接着看看 appendFloat32、appendTime 等函数。
而一个变长的 Column 可以用下图表示:
我们以 appendString 为例来看看如何追加一个变长类型的数据:
上面第 1 步在 appendString 这个函数中完成,第 2、3 步在 finishAppendVar 这个函数中完成。其他边长类型元素的追加操作也是非常相似,感兴趣的同学可以接着看看 appendBytes、appendJSON 等函数。
我们使用 appendNull 函数来向一个 Column 中追加一个 NULL
值:
如上图所示:Chunk 中的 Row 是一个逻辑上的概念:Row 中的数据存储在 Chunk 的各个 Column 中,同一个 Row 中的数据在内存中没有连续存储在一起,我们在获取一个 Row 对象的时候也不需要进行数据拷贝。提供 Row 的概念是因为算子运行过程中,大多数情况都是以 Row 为单位访问和操作数据,比如聚合,排序等。
Row 提供了获取 Chunk 中数据的方法,比如 GetInt64、GetString、GetMyDecimal 等,前面介绍了往 Column 中 append 数据的方法,获取数据的方法可以由 append 数据的方法反推,代码也比较简单,这里就不再详细介绍了。
目前 Chunk 这个包只对外暴露了 Chunk, Row 等接口,而没有暴露 Column,所以,写数据调用的是在 Chunk 上实现的对 Column 具体函数的 warpper,比如 AppendInt64;读数据调用的是在 Row 上实现的 Getxxx 函数,比如 GetInt64。
在重构前,TiDB 1.0 中使用的执行框架会不断调用 Child 的 Next 函数获取一个由 Datum 组成的 Row(和刚才介绍的 Chunk Row 是两个数据结构),这种执行方式的特点是:每次函数调用只返回一行数据,且不管是什么类型的数据都用 Datum 这个结构体来封装。
这种方法的优点是:简单、易用。缺点是:
在重构后,TiDB 2.0 中使用的执行框架会不断调用 Child 的 NextChunk 函数,获取一个 Chunk 的数据。
这种执行方式的特点是:
tidb_max_chunk_size
的 session 变量来控制,默认是 1024 行。因为 TiDB 是一个混合 TP 和 AP 的数据库,对于 AP 类型的查询来说,因为计算的数据量大,1024 没啥问题,但是对于 TP 请求来说,计算的数据量可能比较少,直接在一开始就分配 1024 行的内存并不是最佳的实践( 这里 有个 github issue 讨论这个问题,欢迎感兴趣的同学来讨论和解决)。这种执行方式的好处是:
采用了新的执行框架后,OLAP 类型语句的执行速度、内存使用效率都有极大提升,从 TPC-H 对比结果 看,性能有数量级的提升。
作者:张建
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。