本文共 4630 字,大约阅读时间需要 15 分钟。
目录
1 clickhouse关键是要搞懂MergeTree,它的应用场景最广泛,MergeTree系列其他表引擎都是基于MergeTree
2 需要弄清楚的东西主要有分区、索引(一级和二级)、标记文件、数据块,把这些概念搞清楚之后,进而就会明白clickhouse的写入和查询过程,进而就会明白它查询为什么会这么快创建
CREATE TABLE [IF NOT EXISTS] [db_name.]table_name ( name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr], name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr], 省略...) ENGINE = MergeTree() [PARTITION BY expr] [ORDER BY expr] [PRIMARY KEY expr] [SAMPLE BY expr] [SETTINGS name=value, 省略...]1 PARTITION:指定表数据以何种标准进行分区;分区键既可以是单个列字段,也可以通过元组的形式使用多个列字段,同时它也支持使用列表达式2 ORDER BY:在一个数据片段内,数据以何种标准排序;默认情况下主键(PRIMARY KEY)与排序键相同 3 PRIMARY KEY:会根据主键字段生成一级索引,用于加速表查询4 index_granularity:索引粒度,默认8192,即每间隔8192行数据才生成一条索引
存储
MergeTree创建后文件作用介绍
checksums.txt:校验文件,用于快速校验文件的完整性和正确性columns.txt:列信息文件,保存分区列字段信息count.txt:计数文件,当前数据分区目录下数据的总行数primary.idx:一级索引文件[Column].bin:数据文件,使用压缩格式存储,默认为LZ4压缩格式,用于存储某一列的数[Column].mrk:列字段标记文件,保存了.bin文件中数据的偏移量信息[Column].mrk2:使用了自适应大小的索引间隔,则标记文件会以.mrk2命名partition.dat与minmax_[Column].idx:如果使用了分区键,例如PARTITION BY EventTime,则会额外生成partition.dat与minmax索引文件,partition.dat用于保存当前分区下分区表达式最终生成的值;而minmax索引用于记录当前分区下分区字段对应原始数据的最小和最大值。例如EventTime字段对应的原始数据为2019-05-01、2019-05-05,分区表达式为PARTITION BY toYYYYMM(EventTime)。partition.dat中保存的值将会是2019-05,而minmax索引中保存的值将会是2019-05-012019-05-05数据查询时能够快速跳过不必要的数据分区目录,减少最终需要扫描的数据范围skp_idx_[Column].idx与skp_idx_[Column].mrk:如果在建表语句中声明了二级索引,则会额外生成相应的二级索引与标记文件
数据类型的不同,分区ID的生成逻辑目前拥有四种规则:
(1)不指定分区键:如果不使用分区键,则分区ID默认取名为all,所有的数据都会被写入这个all分区。 (2)使用整型:如果分区键取值属于整型(兼容UInt64,包括有符号整型和无符号整型),且无法转换为日 期类型YYYYMMDD格式,则直接按照该整型的字符形式输出,作为分区ID的取值。 (3)使用日期类型:如果分区键取值属于日期类型,或者是能够转换为YYYYMMDD格式的整型,则使用按照 YYYYMMDD进行格式化后的字符形式输出,并作为分区ID的取值。 (4)使用其他类型:如果分区键取值既不属于整型,也不属于日期类型,例如String、Float等,则通过 128位Hash算法取其Hash值作为分区ID的取值分区名称构成 = 分区ID _ 最小数据块 _ 编号最大数据块编号 _ 合并层级
例如201905_1_1_0分区构成:
PartitionID_MinBlockNum_MaxBlockNum_Level 1 BlockNum是一个整型的自增长编号。自增长编号n在单张MergeTree数据表内全局累加,n从1开始,每当新创建一个分区目录时,计数n就会累积加1。对于一个新的分区目录而言,MinBlockNum与MaxBlockNum取值一样,同等于n,例如201905_1_1_0、201906_2_2_0以此类推 2 level:表示合并过的次数MergeTree的主键使用PRIMARY KEY定义,待主键定义之后,MergeTree会依据index_granularity间隔(默认8192行),为数据表生成一级索引并保存至primary.idx文件内,索引数据按照PRIMARY KEY
排序。相比使用PRIMARY KEY定义,更为常见的简化形式是通过ORDER BY指代主键。在此种情形下,PRIMARY KEY与ORDER BY定义相同,所以索引(primary.idx)和数据(.bin)会按照完全相同的规则 排序1 稀疏索引的优势是显而易见的,它仅需使用少量的索引标记就能够记录大量数据的区间位置信息,且数据量越大优势越为明显。
2 MergeTree只需要12208行索引标记就能为1亿行数据记录提供索引 3 稀疏索引占用空间小,primary.idx内的索引数据常驻内存,取用速度自然极快1 index_granularity表示索引的粒度,它表示每隔多少行取一次值作为索引值
2 index_granularity作用于一级索引(.idx),同时也会影响数据标记(.mrk)和数据文件(.bin)查询的时候要借助数据标记才能定位数据要搞懂primary.idx文件里面内容的含义
可以看下图理解 例如第0(8192*0)行CounterID取值57,第8192(8192*1)行CounterID取值1635,而第 16384(8192*2)行CounterID取值3266,最终索引数据将会是5716353266多个主键也是类似,例如ORDER BY(CounterID,EventDate),则每间隔8192行可以同时取CounterID与EventDate两列的值作为索引值
1 二级索引又称跳数索引
2 跳数索引的目的与一级索引一样,也是帮助查询时减少数据扫描的范围。1 完整的语法定义
INDEX index_name expr TYPE index_type(...) GRANULARITY granularity 2 跳数索引会额外生成相应的索引与标记文件(skp_idx_[Column].idx与skp_idx_[Column].mrk)1 对于跳数索引而言,index_granularity定义了数据的粒度,而granularity定义了聚合信息汇总的粒度。换言之,granularity定义了一行跳数索引能够跳过多少个index_granularity区间的数据。
1 MergeTree共支持4种跳数索引,分别是minmax、set、ngrambf_v1和tokenbf_v122 一张表可同时支持多个跳数索引CREATE TABLE skip_test ( ID String, URL String, Code String, EventTime Date, INDEX a ID TYPE minmax GRANULARITY 5, INDEX b(length(ID) * 8) TYPE set(2) GRANULARITY 5, INDEX c(ID,Code) TYPE ngrambf_v1(3, 256, 2, 0) GRANULARITY 5, INDEX d ID TYPE tokenbf_v1(256, 2, 0) GRANULARITY 5)ENGINE = MergeTree()
数据存储这块需要搞清楚MergeTree引擎各列是独立存储的且数据是经过压缩的
MergeTree也并不是一股脑地将数据直接写入.bin文件,而是经过了一番精心设计:
首先,数据是经过压缩的,目前支持LZ4、ZSTD、Multiple和Delta几种算法,默认使用LZ4算法; 其次,数据会事先依照ORDER BY的声明排序; 最后,数据是以压缩数据块的形式被组织并写入.bin文件中的。如果把MergeTree比作一本书,primary.idx一级索引好比这本书的一级章节目录,.bin文件中的数据好比这本书中的文字,那么数据标记(.mrk)会为一级章节目录和具体的文字之间建立关联。
对于数据标记而言,它记录了两点重要信息: 其一,是一级章节对应的页码信息; 其二,是一段文字在某一页中的起始位置信息。 这样一来,通过数据标记就能够很快地从一本书中立即翻到关注内容所在的那一页,并知道从第几行开始阅读。 数据标记特征 数据标记和索引区间是对齐的,均按照index_granularity的粒度间隔。通过索引区间的下标编号就可以直接找到对应的数据标记。 见下图整理一下数据写入过程是怎么样的,就是要搞清楚分区目录,一级索引、二级索引,数据文件在数据写入的过程中是怎么变化的
随着数据的写入
1 生成分区目录 2 新数据写入,生成新的分区目录 3 相同分区的目录会依照规则合并 4 生成primary.idx一级索引(如果声明了二级索引,还会创建二级索引文件) 5 每一个列字段的.mrk数据标记和.bin压缩数据文件查询的原理:MergeTree可以依次借助分区索引、一级索引和二级索引,将数据扫描范围缩至最小。然后再借助数据标记,将需要解压与计算的数据范围缩至最小,如果一条查询语句没有指定任何WHERE条件,或是指定了WHERE条件,但条件没有匹配到任何索引(分区索 引、一级索引和二级索引),那么MergeTree就不能预先减小数据范围。在后续进行数据查询时,它会扫描所有分区目录,以及目录内索引段的最大区间。虽然不能减少数据范围,但是MergeTree仍然能够借助数据标记,以多线程的形式同时读取多个压缩数据块,以提升性能
朱凯《ClickHouse原理解析与应用实践》
如果你觉得文章还可以,欢迎点赞,评论,转发,收藏,关注哦!!!
转载地址:http://mqzzz.baihongyu.com/