ClickHouse高级学习(三)Clickhouse引擎详解(待更新)

MergeTree的原理解析

  • 先来看看MegreTree数据表语句
    在这里插入图片描述
    • partition by:分区键,用于表示数据以何种标准进行分区
    • order by:排序键,用于指定一定数据片段内,如何进行排序,默认情况下跟主键一致,如果排序键是元组的话,会先根据第一个元素进行排序,然后以此类推
    • primary key:主键,声明后会依照主键生成一级索引,用于加速表查询。主键与排序键(ORDER BY)相同,所以通常直接使用ORDER BY代为指定主键,无须刻意通过PRIMARY KEY声明。并且MegreeTree允许主键存在重复数据
    • sample by:抽样表达式,用于声明数据以何种标准进行采样
    • settings:配置参数
      • index_granularity:表示索引的粒度,默认值为8192
      • index_granularity_bytes:表示自适应间隔大小得特性,动态划分间隔大小
      • enable_mixed_granularity_parts:设置是否开启自适应索引间隔的功能,默认开启
      • merge_with_ttl_timeout:数据TTL的功能
      • storage_policy:多路径的存储策略
  • 现在来看看MergeTree存储结构,先来看看在磁盘上的存储结构
    在这里插入图片描述
    • checksums.txt:校验文件,用于保存各类文件的大小和哈希值,用于开苏校验文件的完整性和正确性
    • columns.txt:列信息文件,用于保存此数据分区下的列字段信息
    • count.txt:计数文件,记录当前数据分区目录下数据的总行数
    • primary.idx:一级索引文件,用于存放稀疏索引
    • [Column].bin:数据文件,使用压缩格式存储,默认是LZ4压缩格式,用于存储某一列的数据
    • [Column].mrk:列字段标记文件,标记文件保存了.bin文件中数据的偏移量信息,所以标记文件与.bin文件的数据一一对应,又因为这个文件跟primary.idx一一对应,所以这个文件建立起了索引跟数据文件的对应。
    • [Column].mrk2:如果使用了自适应大小的索引间隔,则标记文件会以.mrk2命名
    • partition.dat与minmax_[Column].idx:这两个文件是只有使用了分区键才会产生
      • partion.dat:用于保存当前分区下分区表达式最终生成的值
      • minmax_[Column].idx:用于记录当前分区下分区对应原始数据的最小值和最大值
    • skp_idx_[Column].idx与skp_idx_[Column].mrk:如果在
      建表语句中声明了二级索引,则会额外生成相应的二级索引与标记文

数据分区

  1. 数据分区规则:由分区ID决定,具体到每个分区对应的ID是由分区键决定的,分区键是由一个或一组字段表达式声明。
  2. 分区ID的生成逻辑规则:
    • 不指定分区键:不指定的话就会生成一个ALL分区,所有的数据都会被写入这个ALL分区内
    • 使用整形:直接按照该整形的字符形式作为分区ID的取值
    • 使用日期:按照YYYTMMDD的形式进行格式转换变成字符串输出
    • 使用其他类型:通过128位Hash算法取其Hash值进行分区ID的取值
    • 多个分区字段:依旧跟上面四个生成分区ID 不过 分区ID之间通过 ‘-’ 依次进行拼接
  3. 分区目录命名规则:Megre Tree 分区目录完整的物理名称是入下图所示,ID之后还跟着一些数字
    在这里插入图片描述
    • PartitionID :分区ID
    • MinBlockNum:最小数据块标号,整型的自增长编号,当分区目录发生合并时,对于新产生合并目录会更新
    • MaxBlockNum:最大数据块标号,整形的自增长编号,当分区目录发生合并时,对于新产生合并的目录会更新
    • Level:合并等级,分区合并的次数,也可以理解为就是版本号,因为合并的时候需要判断是否是旧的数据还是需要合并的数据,因为合并之后旧的数据不会立即删除会在某个特定时间点在删除
  4. 分区目录合并的过程:
    • 首先分区目录是在数据写入的时候才会被创建的,追加数据后目录本身不会发生变化,只是会添加新的数据文件。

    • 所以同一个分区会有多个分区目录的情况,在某个时刻会通过后台任务将属于相同分区的目录进行合并,形成新的目录。

      • MinBlockNum:取同一分区内所有目录中最小的MinBlockNum值
      • MaxBlockNum:取同一分区内所有目录中最大的MaxBlockNum值
      • Level:取同一分区内最大Level值并加1
        在这里插入图片描述
    • 已经合并的目录不会立即删除,会在某个时刻通过后台任务删除

一级索引

  1. primary.idx 的一级索引采用的是稀疏索引,什么稀疏索引,就是每一行索引标记的是一段数据,有稀疏索引就有稠密索引,稠密索引顾名思义就是一行索引对应一行数据。
    • 优势:通过少量的索引就可以标记多个数据区间信息
  2. 索引粒度:primary.idx中有个参数,是用来设置索引粒度的,就是index_granularity,默认是8192。什么是索引粒度,索引粒度简单来说就是稀疏索引最大记录数,因为每个索引会对应一个数据段,这个数据段能存储多大,这就是索引粒度。
  3. 索引数据的生成规则:因为是稀疏索引,所以MegreTree需要间隔index_granularity行数据才会生成一条索引记录,其索引值会依据声明的主键字段获取,然后存储到primmary.idx文件进行保存
  4. 索引查询过程:假设现在有一份数据,数据有192行记录,主键ID为string,ID取值从B000开始,一直到B192为止,并且当前的索引粒度为3,所以primary.idx文件中的数据会如此:B000B003B006B009…B189B192,然后会将此数据片段划分成192/3 = 64个小的MarkRange
    • 生成查询条件区间:首先,将查询条件转换为条件区间。即便是单个值的查询条件,也会被转换成区间的形式
    • 递归交集判断:以递归的形式,依次对MarkRange的数值区间与条件区间做交集判断。
      • 如果不存在交集,则直接通过剪枝算法优化整段MarkRange
      • 如果存在交集,且MarkRange大于8,则将此区间进一步拆分成8个子区间,并重复此规则,继续做递归交集判断
      • 如果存在交集,且MarkRange不可再分解。则记录MarkRange并返回
    • 合并MarkRange区间:将最终匹配的MarkRange聚在一起,合并它们的范围

二级索引

数据存储

  1. 首先clickhouse是列式存储,是具体到每一个字段的,数据也是独立存储,每个字段也会有对应的.bin数据文件。
  2. 存放方式是按照分区目录的形式进行存放,再根据索引对数据进行排序,然后再进行标记
  3. 然后进行数据压缩
  4. 压缩数据块是由头信息和压缩数据两部分组成。头信息固定使用9位字节表示,具体由1个UInt8整形和2个UInt32整形组成,分别代表使用压缩算法类型,压缩后的数据大小和压缩前的数据大小
    在这里插入图片描述
  5. .bin压缩文件是由多个压缩数据块组成的,每个压缩数据块的体积,按照其压缩前的数据字节大小,都被严格控制在64KB~1MB,其上下限分别由min_compress_block_size 与 max_compress_block_size 参数指定,而压缩数据块最终大小,则和一个间隔内数据的实际大小相关
  6. 数据具体写入的过程中,会依照索引粒度,按批次获取数据并进行处理。如果把一批数据的未压缩大小设为size,则整个写入过程遵循以下规则:
    • 单批次数据size < 64kb:如果单个批次数据小于64kb,则继续获取下一批数据,直至累积到size >= 64kb,生成下一个压缩数据块。
    • 单批次数据64kb <= size <= 1mb:直接生成下一个压缩数据块
    • 单批次数据size > 1mb:先按照1mb进行截断,并生成下一个压缩数据块。剩余数据继续依照上述规则执行。
  7. 引入压缩块的目的有两个:
    • 虽然数据被压缩后能够有效减少数据大小,降低存储空间并加速数据传输效率,但数据的压缩和解压动作,其本身也会带来额外的性能损耗,所以需要控制被压缩数据的大小,以求在性能损耗和压缩率之间寻求一种平衡。
    • 可以不通过解压所有.bin文件来读取想要的数据,减少了数据遍历的数据量。

数据标记

  1. 什么是数据标记,就是为了衔接一级索引和实际数据的桥梁,将索引跟实际数据一一对应在一起
  2. 一行标记数据使用一个元组表示,元组内包含两个整形数值的偏移量信息。它们分别表示在此段数据区间内,在对应的.bin压缩文件中,压缩数据块的起始偏移量;以及将该数据压缩块解压后,其未压缩数据的起始偏移量。
  3. 在读取数据时,必须通过标记数据的位置信息才能够找到所需要的数据。整个查找过程大致可以分为读取压缩数据块和读取数据两个步骤。
    • 读取压缩数块:无需加载.bin文件所有的数据,只需要加载特定的压缩数据块,而如何判断这是我们需要的呢,这时候就会有标记文件可以提供我们需要数据的偏移量。随后就开始解压对应的压缩数据块
    • 读取数据:解压之后,依旧不需要扫描全部的压缩数据,只需要根据索引粒度找寻对应的数据,这个时候还是根据数据标记文件,来寻找。

整个写入流程

  1. 生成分区目录,伴随着每一批数据的写入,都会生成一个新的分区目录。在后续的某一时刻,属于相同分区的目录会依照规则合并到一起;
  2. 按照index_granularity索引粒度,会分别生成primary.idx一级索引(如果声明了二级索引,还会创建二级索引文件)、每一个列字段的.mrk数据标记和.bin压缩数据文件。

整个查询流程

  1. 首先借助分区索引,找到对应的分区
  2. 再根据一级索引和二级索引确定标记文件中数据的范围
  3. 根据标记文件中对应的范围去解压对应的压缩文件
  4. 解压完的数据后,再根据需求读取数据

总结

Merge Tree系列表引擎

ReplacingMergeTree

  1. 首先MegreTree有主键,但是却没有主键约束,但是有些场合并不希望有重复数据,这个时候就需要用到ReplacingMergeTree引擎,但也只是一定程度上解决
  2. 创建 ReplacingMegreTree 引擎的表,跟创建普通的表一样,将ENGINE = 改成 ENGINE = ReplacingMegreTree (ver) , ver是选天填参数,作为版本号在这里插入图片描述
  3. 排序键Order By所声明的表达式是后续作为判断数据是否重复的依据
  4. 注意,ReplacingMergeTree只会以分区为单位删除重复数据,只有相同数据分区内的重复数据才可以被删除
  5. 只有在合并分区的时候才会触发删除重复数据的逻辑。
  6. 在进行数据去重时,因为分区内的数据已经基于ORBER BY进行了排序,所以能够找到那些相邻的重复数据。
  7. 数据去重策略有两种:
    • 如果没有设置ver版本号,则保留同一组重复数据中的最后一行。
    • 如果设置了ver版本号,则保留同一组重复数据中ver字段取值最大的那一行。

SummingMergeTree

  1. 用 order by 排序键作为聚合数据的条件Key
  2. 只有在合并分区的时候才会触发汇总的逻辑
  3. 以数据分区为单位来聚合数据,当分区合并时,同一数据分区内聚合Key相同的数据会被合并汇总,而不同分区之间的数据则不会被汇总
  4. 如果在定义引擎时指定了columns汇总列,则sum汇总这些列字段,如果未指定,则聚合所有非主键的数值类型字段
  5. 进行数据汇总时,因为分区内的数据已经基于Order by排序,所以能够找到相邻且拥有相同聚合key的数据
  6. 在汇总数据时,同一分区内,相同聚合Key的多行数据会合并成一行。其中,汇总字段会进行SUM计算;对于那些非汇总字段,则会使用第一行数据的取值。
  7. 支持嵌套结构,但列字段名称必须以Map后缀结尾。嵌套类型中,默认以第一个字段作为聚合Key。除第一个字段以外,任何名称以Key、Id或Type为后缀结尾的字段,都将和第一个字段一起组成复合Key。

AggregatingMergeTree

  1. 这个引擎也是预先将明细数据聚合成结果数据,然后让用户查询的时候直接查询出你想要的结果数据,用空间换时间的方法提升查询性能。根据预先定义的聚合函数计算数据并通过二进制的格式存入表内。将同一分组下的多行数据聚合成一行,既减少了数据行,又降低了后续聚合查询的开销。
  2. 使用方式:ENGINE = AggregatingMergeTree()
    • AggregatingMergeTree 没有额外的设置参数,在分区合并时,在每个数据分区内,会按照Order by 聚合。而使用何种聚合函数,以及针对哪些列字段计算,则是通过定义AggregatingMergeTree 数据类型实现。
      在这里插入图片描述
    • 上面的语句可以看出 id,city 是排序键,id 是主键,字段中code和value是聚合字段,相当于uniq(code)去重 , sum(value)聚合,而其中
      AggregateFunction是一种特殊的数据类型,能够以二进制的形式存储中间状态结果。对于AggregateFunction类型的列字段,在写入数据时,需要调用State函数;而在查询数据时,则需要调用相应的Merge函数。
      在这里插入图片描述
    • 这样子看来AggregatingMergeTree使用十分繁琐,但是这个引擎的最主流的方法是用作物化视图,而物化视图是作为其他数据表上层的一种查询视图,操作流程如下
      在这里插入图片描述
      在这里插入图片描述
  3. 总结:
    • 用ORBER BY排序键作为聚合数据的条件Key。
    • 使用AggregateFunction字段类型定义聚合函数的类型以及聚合的字段。
    • 只有在合并分区的时候才会触发聚合计算的逻辑。
    • 以数据分区为单位来聚合数据。当分区合并时,同一数据分区内聚合Key相同的数据会被合并计算,而不同分区之间的数据则不会被计算。
    • 在进行数据计算时,因为分区内的数据已经基于ORBER BY排序,所以能够找到那些相邻且拥有相同聚合Key的数据。
    • 在聚合数据时,同一分区内,相同聚合Key的多行数据会合并成一行。对于那些非主键、非AggregateFunction类型字段,则会使用第一行数据的取值。
    • AggregateFunction类型的字段使用二进制存储,在写入数据时,需要调用State函数;而在查询数据时,则需要调用相应的Merge函数。其中,*表示定义时使用的聚合函数。
    • AggregatingMergeTree通常作为物化视图的表引擎,与普通MergeTree搭配使用。

CollapsingMergeTree

VersionedCollapsingMergeTree

其他常见类型表引擎

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值