亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行( 二 )


亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行

文章插图
 
而 Clickhouse 的数据架构类似关系型数据库,其中包括了解析器,主要负责将 SQL 语句通过词法分析、语法分析等转换成计算机可读的抽象语法树 。另外还有优化器,逻辑优化负责优化抽象语法树的逻辑,比如简化一些长难运算表达式,做一些语义优化等 。物理优化则负责生成可以直接执行的物理执行计划,指导数据库管理系统如何获取数据表、如何进行数据 join、排序等等 。
Clickhouse 的物理执行计划可认为是一个数据流图,也就是数据的有向无环图 。在这个图里,数据从一个管道传到另一个管道,也就是从一个操作符传到另一个操作符 。查询执行器是用来执行计划的引擎,它会从存储引擎中取出数据,并返回给客户端 。
亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行

文章插图
 
如上图,Clickhouse 在启动时加载配置信息,然后根据不同的解析协议监听不同的服务端口 。客户端发送来 SQL 请求后,首先它会对 SQL 进行语法解析,然后生成抽象语法树,并进行一系列的逻辑优化、物理优化,生成执行计划 。接下来由不同的执行器根据 SQL 请求来将执行计划分发到本地或远端的存储引擎,从存储引擎中取出数据 。数据经过一系列的计算加工后返回给客户端,客户端就可以输出缓冲区读取查询结果 。
MergeTree 存储过程
相比 InnoDB 使用的 B+Tree,Clickhouse 使用的是 MergeTree 存储引擎来存储数据 。这里以一个 Clickhouse 表为例:
亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行

文章插图
 
本例中,我们根据出生日期做一个数据分区,主键选用用户的名字,并设置 SETTINGS index_granularity=3 。表建成后插入 10 条记录,分为 2001 年 3 月和 2001 年 2 月两个数据区间 。表建完、数据写完以后,Clickhouse 默认会在数据文件存放路径下建一个相应的表名:
亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行

文章插图
 
这里可以看到,10 条数据分了两个文件夹来存储 。文件夹命名时,其第一部分是分区键,也就是出生日期;1_1(2_2)代表每个数据分区内数据块最小块和最大块的编号 。最后的数字 0 代表合并层级 。
亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行

文章插图
 
上图是 MergeTree 中对 Data part 进行元数据管理的结构体 。其中,partition id 代表数据所处的分区 id;min block、max block 代表数据写入的版本信息——用户每次批量写的数据都会生成一个 Data part,同一批写入的数据会被标记为唯一的 block number 。MergeTree 存储引擎后台会定期通过异步任务合并数据,且只会合并位于同一个数据分区内的数据,还要求 min block 和 max block 数据区间必须是连续非重合的 。
第四个 level 字段默认新插入的数据都是 0,之后会随着合并次数的增加在原来的基础上依次增大 。下面的 mutation 字段在数据更正时使用 。如果要进行数据的更正操作,Clickhouse 会默认给 mutation 字段进行标记和更新 。
亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行

文章插图
 
虽然测试数据只有一张表 10 条数据,但它会在磁盘目录上生成大量文件 。具体来说,Clickhouse 默认每一个列生成一个文件,默认数据文件放在 bin 文件里 。每一个数据分析目录下生成一个 count 文件,记录分区里有多少行数据 。
亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行

文章插图
 
本例中,建表时设置的 SETTINGS index_granularity 设为 3 。插完数据以后观察主键索引,可以发现它会把主键以每 3 条记录为一个区间,将主键信息存储在 primary.idx 里 。
亿级数据库毫秒级查询?看完这一篇,海量数据赋能你也行

文章插图
 
结合前文例子来看数据全景 。假设下面绿颜色的就是要写入的一批数据,存放用户的名字;假设每个名字占用 4 个字节,可以看到绿颜色上边有一个 granule,写的是 8192 。指定 granule 是 8192 之后,数据在写入时会放到一个具有缓冲区的 OutPort 流中,按照一个 granule 一个 granule 来写;写完第一个 granule,当发现这个缓冲区内数据大小超过 64KB,这时就会把数据进行压缩落盘,放在下边的粉红色文件块里 。落数据块时会先写一个文件头,文件头由三部分组成,如上图所示 。
第二段 8192 的数据,压缩完之后数据块可能比较长一些;可以发现,数据每次写入就会产生两个文件 。一个是 bin 文件,也就是压缩后的数据文件 。另一个文件就是主键 index 文件 。但这样以来,在数据查询时不知道数据究竟在数据文件里的哪一块,不知道该怎么拆分 bin 文件,如果把整个 bin 文件都加载内存以后扫描,效率是会很差的 。为了解决这个尴尬的问题 Clickhouse 引入了 mrk 文件 。写数据文件的时候会把 bin 文件头信息写到 mrk 文件里 。比如说第一块数据写完之后,会把起始位置、解压缩后的位置、解压缩前的位置放在 mrk 这个文件块里, 作为一行记录 。查询时直接根据主键 index 记录的偏移量找到对应的 mrk 记录的某段数据的起始位置,之后读取数据即可 。


推荐阅读