MergeTree 在写入一批数据时数据总会以数据片段的形式写入磁盘且数据片段不可修改。ClickHouse 会通过后台线程定期合并这些数据片段属于相同分区的数据片段会被合成一个新的片段。这种数据片段往复合并的特点也正是合并数据名称的由来CREATE TABLE table_name ( ...字段省略 ) ENGINE MergeTree() [PARTITION BY xx] [ORDER BY xx] [PRIMARY KEY xx] [SAMPLE BY xx] [SETTINGS namevalue]1PARTITION BY [选填]分区键用于指定表数据以何种标准进行分区。2ORDER BY [必填]排序键用于指定在一个数据片段内数据以何种标准排序。默认情况下主键PRIMARY KEY与排序键相同。排序字段既可以是单个列字段也可以通过元组的形式使用多个列字段顺序以ORDER BY 后面的字段先后顺序来排序。3PRIMARY BY [选填]主键顾名思义声明后会依照主键字段生成以及索引用于加速表查询。4SAMPLE BY [选填]抽样表达式用于声明数据以何种标准进行采样。5SETTINGSindex_granularity [选填]index_granularity 对于 MergeTree 而言是一项非常重要的参数他表示索引的粒度默认值为 8192。也就是说MergeTree 的索引在默认情况下每间隔 8192 行数据才生成一条索引6SETTINGSindex_granularity_bytes [选填]index_granularity_bytes 每一批次写入数据的体量大小自适应间隔大小根据每一批次写入数据的体量大小动态划分间隔大小默认为 10M10*1024*1024MergeTree 的存储结构MergeTree 表引擎中的数据是拥有物理存储的数据会按照分区目录的形式保存到磁盘之上其完整的存储结果如下所示一张数表的完整物理结构分3个层级依次是数据表目录、分区目录及各分区下具体的额数据文件。1partition分区目录余下各类数据文件都是以分区目录的形式被组织存放的属于相同分区的数据最终会被合并到同一个分区目录。2checksum.txt校验文件使用二进制格式存储。保存各类文件primary.idx、count.txt等的 size 大小及 size 的哈希值用于快速检验文件的完整性和正确性。3columns.txt列信息文件。4count.txt计数文件记录当前数据分区目录下数据总行数。5primary.idx一级索引文件用于存放稀疏索引。6[Column].bin数据文件使用压缩格式存储默认为 LZ4 压缩格式用于存储某一列数据。多个列就有多个 .bin 文件7[Column].mrk列字段标记文件标记文件中保存 .bin 文件中数量的偏移量信息。首先通过 primary.idx 找到对应数据偏移量然后再通过偏移量直接从 .bin 中读取数据。.mrk 标记文件和 .bin 文件一一对应。8[Column].mrk2 如果使用自适应大小的索引间隔则标记文件会以 .mrk2 命名作用原理和 .mrk 一样。9partition.dat 与 minmax_[Column].idx 用了分区键会产生partition.dat 用于保存当前分区下分区表最终生成的值minmax 索引用于记录当前分区下分区字段对应原始数据的最小和最大值查询的时候可快速跳过不必要的分区目录减少数据扫描范围。10skp_idx_[Column].idx 与 skp_idx_[Column].mrk 如果建表语句中声明了二级索引则会额外生成相应的二级索引与标记文件。二级索引又称跳数索引。分区目录的命名规则1PartitionID**分区ID这里是具体的日期。2MinBlockNum 和 MaxBlockNum**最小数据块编号与最大数据块编号。计数在单张 MergeTree 数据表内全局累加。3Level**合并的层级相同分区发生合并则相应分区内计数累计加1分区目录合并过程1MergeTree 分区目录不是在数据表创建后就存在的而是在数据写入过程中被创建的。2伴随着每一批数据的写入MergeTree 都会生成一批新的分区目录索引粒度稀疏索引的优势在于使用少量的索引标记就能够记录大量数据的区间位置信息而且数据量越大优势越为明显索引生成索引查询1生成查询条件区间首先将查询条件转换为条件区间。一个具体的数据段是一个 MarkRange划分依据是间隔默认81922递归交际判断以递归的形式依次对 MarkRange 的数据区间与条件区间做交集判断。3合并 MarkRange 区间将最终匹配的 MarkRange 聚合在一起合并它们的范围二级索引MergeTree 支持二级索引二级索引又称跳数索引由数据的聚合信息构建而成目的也是帮助查询减少数据的扫描范围。index_granularity按照设置的粒度值的大小将数据分成 n 段总共有 [0, n-1] 个区间ntotal_rows/index_granularity。granularity定义了一行跳数索引能够跳过多少个 index_granularity 区间的数据数据存储数据按列存储具体到每一列数据也是独立存储的每个列字段都拥有一个与之对应的.bin数据文件数据写入之前是经过压缩的目前支持 LZ4、ZSTD、Multiple 和 Delta 几种算法默认使用 LZ4 算法数据会事先依照 ORDER BY 的声明排序最后以压缩数据块的形式被组织并写入 .bin 文件中。压缩数据块示意图MergeTree 在数据具体的写入过程中会依照索引粒度默认情况下每次取 8192 行按批次获取数据并处理。如果把一批数据的未压缩大小设置为 size切割过程则如下图所示。切割压缩数据块的逻辑示意图在 .bin 文件中引入压缩数据块的目的1 数据压缩后可以有效减少数据大小但是数据压缩解压会带来性能损耗控制压缩数据的大小以求在性能损耗和压缩率之间寻求一种平衡2读取数据文件的时候可以不用读取整个 .bin 文件缩小数据读取的范围数据标记数据标记根据便宜读取赌赢的压缩数据块以 index_granularity 粒度加载特定的一小段1通过索引下标编号找到对应的数据标记2标记数据示意图3JavaEnable 字段的标记文件和压缩文件的对应关系写入过程1生成分区目录写入第一批数据2相同分区的目录依照规则合并到一起3按照 index_granularity 索引粒度生成 primary.idx 一级索引、二级索引、每一列的 .mrk 数据标记、.bin压缩文件。分区目录、索引、标记和压缩数据的生成过程示意图查询过程1依次借助分区索引、一级索引、二级索引将数据扫描范围缩至最小2借助数据标记将需要解压与计算的数据范围缩小至最小将扫描数据范围最小化的过程
