前文如下:
11.【clickhouse】ClickHouse从入门到放弃-概述
12.【clickhouse】ClickHouse从入门到放弃-环境搭建
13.【clickhouse】ClickHouse从入门到放弃-引擎
14.【clickhouse】ClickHouse从入门到放弃-实战
55.【clickhouse】ClickHouse从入门到放弃-概念场景
56.【clickhouse】ClickHouse从入门到放弃-架构概述
57.【clickhouse】ClickHouse从入门到放弃-update和delete的使用
58.【clickhouse】ClickHouse从入门到放弃-数据类型转换
59.【clickhouse】ClickHouse从入门到放弃-分区表
60.【clickhouse】ClickHouse从入门到放弃-MergeTree的创建方式
61.【clickhouse】ClickHouse从入门到放弃-MergeTree的存储结构
62.【clickhouse】ClickHouse从入门到放弃-数据分区
63.【clickhouse】ClickHouse从入门到放弃-一级索引
64.【clickhouse】ClickHouse从入门到放弃-二级索引
65.【clickhouse】ClickHouse从入门到放弃-数据存储
文档参考:《ClickHouse原理解析与应用实践(数据库技术丛书)(朱凯)》
1. 数据标记
如果把MergeTree比作一本书,primary.idx一级索引好比这本书的一级章节目录,.bin文件中的数据好比这本书中的文字,那么数据标记(.mrk)会为一级章节目录和具体的文字之间建立关联。对于数据标记而言,它记录了两点重要信息:其一,是一级章节对应的页码信息;其二,是一段文字在某一页中的起始位置信息。 这样一来,通过数据标记就能够很快地从一本书中立即翻到关注内容所在的那一页,并知道从第几行开始阅读。
1.1 数据标记的生成规则
数据标记作为衔接一级索引和数据的桥梁,其像极了做过标记小抄的书签,而且书本中每个一级章节都拥有各自的书签。它们之间的关系如下图所示。
为了能够与数据衔接,数据标记文件也与.bin文件一一对应。即每一个列字段[Column].bin文件都有一个与之对应的[Column].mrk数据标记文件,用于记录数据在.bin文件中的偏移量信息。
一行标记数据使用一个元组表示,元组内包含两个整型数值的偏移量信息。它们分别表示在此段数据区间内,在对应的.bin压缩文件中,压缩数据块的起始偏移量;以及将该数据压缩块解压后,其未压缩数据的起始偏移量。 下图所示是.mrk文件内标记数据的示意。(64kb一个压缩数据块 64*1024=65535;index_granularity=8192,那么单个数据块会有索引会存8个数据; mrk文件压缩数据块存(压缩数据块的起始偏移量0 和未压缩数据的起始偏移量 65535字节压缩完单个压缩数据块12016字节)) 如图所示,每一行标记数据都表示了一个片段的数据(默认8192行)在.bin压缩文件中的读取位置信息。标记数据与一级索引数据不同,它并不能常驻内存,而是使用LRU(最近最少使用)缓存策略加快其取用速度。
1.2 数据标记的工作方式
MergeTree在读取数据时,必须通过标记数据的位置信息才能够找到所需要的数据。整个查找过程大致可以分为读取压缩数据块和读取数据两个步骤。为了便于解释,这里继续使用测试表hits_v1中的真实数据进行说明。图6-19所示为hits_v1测试表的JavaEnable字段及其标记数据与压缩数据的对应关系。
首先,对图6-19所示左侧的标记数据做一番解释说明。JavaEnable字段的数据类型为UInt8,所以每行数值占用1字节。而hits_v1数据表的index_granularity粒度为8192,所以一个索引片段的数据大小恰好是8192B。按照6.5.2节介绍的压缩数据块的生成规则,如果单个批次数据小于64KB,则继续获取下一批数据,直至累积到size>=64KB时,生成下一个压缩数据块。因此在JavaEnable的标记文件中,每8行标记数据对应1个压缩数据块(1B*8192=8192B,64KB=65536B,65536/8192=8)。所以,从图6-19所示中能够看到,其左侧的标记数据中,8行数据的压缩文件偏移量都是相同的,因为这8行标记都指向了同一个压缩数据块。而在这8行的标记数据中,它们的解压缩数据块中的偏移量,则依次按照8192B(每行数据1B,每一个批次8192行数据)累加,当累加达到65536(64KB)时则置0。因为根据规则,此时会生成下一个压缩数据块。
理解了上述标记数据之后,接下来就开始介绍MergeTree具体是如何定位压缩数据块并读取数据的。
(1)读取压缩数据块: 在查询某一列数据时,MergeTree无须一次性加载整个.bin文件,而是可以根据需要,只加载特定的压缩数据块。而这项特性需要借助标记文件中所保存的压缩文件中的偏移量。
在图6-19所示的标记数据中,上下相邻的两个压缩文件中的起始偏移量,构成了与获取当前标记对应的压缩数据块的偏移量区间。由当前标记数据开始,向下寻找,直到找到不同的压缩文件偏移量为止。此时得到的一组偏移量区间即是压缩数据块在.bin文件中的偏移量。例如在图6-19所示中,读取右侧.bin文件中[0,12016]字节数据,就能获取第0个压缩数据块。
细心的读者可能会发现,在.mrk文件中,第0个压缩数据块的截止偏移量是12016。而在.bin数据文件中,第0个压缩数据块的压缩大小是12000。为什么两个数值不同呢?其实原因很简单,12000只是数据压缩后的字节数,并没有包含头信息部分。而一个完整的压缩数据块是由头信息加上压缩数据组成的,它的头信息固定由9个字节组成,压缩后大小为8个字节。所以,12016=8+12000+8(貌似是前后两个指针),其定位方法如图6-19右上角所示。压缩数据块被整个加载到内存之后,会进行解压,在这之后就进入具体数据的读取环节了。
(2)读取数据: 在读取解压后的数据时,MergeTree并不需要一次性扫描整段解压数据,它可以根据需要,以index_granularity的粒度加载特定的一小段。为了实现这项特性,需要借助标记文件中保存的解压数据块中的偏移量。
同样的,在上图所示的标记数据中,上下相邻两个解压缩数据块中的起始偏移量,构成了与获取当前标记对应的数据的偏移量区间。通过这个区间,能够在它的压缩块被解压之后,依照偏移量按需读取数据。例如在图6-19所示中,通过[0,8192]能够读取压缩数据块0中的第一个数据片段。
索引文件一对一找到对应标记文件,标记文件找到压缩数据块偏移量及解压后数据块偏移量。(索引文件只有一个,数据标记文件也与.bin文件一一对应。即每一个列字段[Column].bin文件都有一个与之对应的[Column].mrk数据标记文件,用于记录数据在.bin文件中的偏移量信息。)