暂无图片
暂无图片
暂无图片
暂无图片
暂无图片
learn-技术分享:ClickHouse:MergeTree系列表引擎.md
319
1次
2023-01-12
5墨值下载

技术分享:ClickHouse:MergeTree系列表引擎

分享目标

  • 了解ClickHouse表引擎分类与作用
  • 了解MergeTree系列表引擎的特性
  • 了解MergeTree系列表引擎的实现机制

ClickHouse表引擎

表引擎的作用

ClickHouse表引擎,实际等同于表的类型,它决定了:

  • 数据的存储方式和位置,写到哪里以及从哪里读取数据
  • 支持哪些查询以及如何支持。
  • 并发数据访问。
  • 索引的使用(如果存在)。
  • 是否可以执行多线程请求。
  • 数据复制参数。

表引擎分类

MergeTree(合并树家族)

在所有的表引擎中,最为核心的当属MergeTree系列表引擎,这些表引擎拥有最为强大的性能和最广泛的使用场合。对于非MergeTree系列的其他引擎而言,主要用于特殊用途,场景相对有限。而MergeTree系列表引擎是官方主推的存储引擎,有主键索引、数据分区、数据副本、数据采样、删除和修改等功能,支持几乎所有ClickHouse核心功能。

该类型的引擎:

  • MergeTree
  • ReplacingMergeTree
  • SummingMergeTree
  • AggregatingMergeTree
  • CollapsingMergeTree
  • VersionedCollapsingMergeTree
  • GraphiteMergeTree

Log(日志)

具有最小功能的轻量级引擎。当需要快速写入许多小表(最多约100万行)并在以后整体读取它们时,该类型的引擎是最有效的。

该类型的引擎:

  • TinyLog
  • StripeLog
  • Log

Integration Engines(集成引擎)

用于与其他的数据存储与处理系统集成的引擎。 该类型的引擎:

  • Kafka
  • MySQL
  • ODBC
  • JDBC
  • HDFS

Special Engines(用于其他特定功能的引擎)

该类型的引擎:

  • Distributed
  • MaterializedView
  • Dictionary
  • Merge
  • File
  • Null
  • Set
  • Join
  • URL
  • View
  • Memory
  • Buffer

MergeTree表引擎

MergeTree家族简介

MergeTree系列表引擎包含

  • MergeTree
  • ReplacingMergeTree
  • SummingMergeTree(汇总求和功能)
  • AggregatingMergeTree(聚合功能)
  • CollapsingMergeTree(折叠删除功能)
  • VersionedCollapsingMergeTree(版本折叠功能)

在这些的基础上还可以叠加Replicated和Distributed。

MergeTree

MergeTree在写入一批数据时,数据总会以数据片段的形式写入磁盘,且数据片段在磁盘上不可修改。为了避免片段过多,ClickHouse会通过后台线程,定期合并这些数据片段,属于相同分区的数据片段会被合成一个新的片段。这种数据片段往复合并的特点,也正是合并树名称的由来。

MergeTree作为家族系列最基础的表引擎,主要有以下特点:

  • 存储的数据按照主键排序:创建稀疏索引加快数据查询速度。
  • 支持数据分区,可以通过PARTITION BY语句指定分区字段。
  • 支持数据副本。
  • 支持数据采样。

MergeTree建表语句:

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],
    ...
    INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
    INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...]
[SETTINGS name=value, ...]

关于以上建表语句的解释如下:

  • ENGINE:选择表引擎

    例如:

    ENGINE = MergeTree()
    

    MergeTree引擎没有参数。必选

  • ORDER BY:排序字段。

    例如:

    ORDER BY (Col1, Col2)
    

    注意,如果没有使用 PRIMARY KEY 显式的指定主键,则ORDER BY排序字段自动作为主键。如果不需要排序,则可以使用 。必选

    ORDER BY tuple() 
    

    语法,这样的话,创建的表也就不包含主键。这种情况下,ClickHouse会按照插入的顺序存储数据。

  • PARTITION BY:分区字段,例如要按月分区,可以使用表达式 toYYYYMM(date_column),

    例如:

    PARTITION BY toYYYYMM(date_column)
    

    表示,对date_column按月分区。这里的date_column是一个Date类型的列,分区名的格式会是"YYYYMM"。可选。

  • PRIMARY KEY:指定主键,如果排序字段与主键不一致,可以单独指定主键字段。否则默认主键是排序字段。

    例如:

    PRIMARY BY (Col1, Col2)
    
    CREATE TABLE default.wqs_test_2
    (
    
    `event_title` String,
    `event_cat` String
    
    )
    ENGINE = MergeTree
    PRIMARY KEY (event_title,event_cat)
    PARTITION BY (event_cat)
    ORDER BY (event_title)
    SETTINGS index_granularity = 8192
    

    大部分情况下不需要再专门指定一个 PRIMARY KEY 子句,注意,**在MergeTree中主键并不用于去重,而是用于索引,加快查询速度。可选。**另外,如果指定了PRIMARY KEY与排序字段不一致,要保证PRIMARY KEY 指定的主键是ORDER BY 指定字段的前缀

    image-20221213162035768

    如图所示,primary key指定为event_title,event_cat 而order by 指定为event_title,报错为主键必须是排序键的前缀。这种强制约束保障了即便在两者定义不同的情况下,主键仍然是排序键的前缀,不会出现索引与数据顺序混乱的问题。

    示例代码:

    CREATE TABLE default.wqs_test_2
    (
    
    `event_title` String,
    `event_cat` String
    
    )
    ENGINE = MergeTree
    PRIMARY KEY (event_title,event_cat)
    PARTITION BY (event_cat)
    ORDER BY (event_title)
    SETTINGS index_granularity = 8192
    
  • SAMPLE BY:采样字段,如果指定了该字段,那么主键中也必须包含该字段。比如 SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))。可选。

  • TTL:数据的存活时间。在MergeTree中,可以为某个列字段或整张表设置TTL。当时间到达时,如果是列字段级别的TTL,则会删除这一列的数据;如果是表级别的TTL,则会删除整张表的数据。可选。

  • SETTINGS:额外的参数配置。可选。 这里参数有很多,我着重说明一下最常用的:

    SETTINGS index_granularity = 8192
    

此配置表示主键索引的颗粒度是8192,即稀疏索引的间隔是8192行一个标记,8192也是稀疏索引的颗粒度默认配置。

接下来我们实践一下

首先创建一个表

CREATE TABLE default.wqs_test_3
(

    `id` UInt8,
    `name` String,
    `age` UInt8,
    `birthday` Date,
    `location` String

)
ENGINE = MergeTree
PARTITION BY toYYYYMM(birthday)
ORDER BY (id,age)
SETTINGS index_granularity = 8192

插入测试数据

insert into default.wqs_test_3 values 
(1,'张三',18,'2021-06-01','上海'),
(2,'李四',19,'2021-02-10','北京'),
(3,'王五',12,'2021-06-01','天津'),
(1,'马六',10,'2021-06-18','上海'),
(5,'田七',22,'2021-02-09','广州');

查询一下

select * from default.wqs_test_3;

image-20221213171019213

可以看到目前包含两个数据块,按日期划分

继续插入一些数据

insert into default.wqs_test_3 values 
(1,'赵八',11,'2021-06-08','北京'),
(2,'李九',19,'2021-02-10','天津'),
(3,'郑十',12,'2021-07-01','北京');

再查询一下(在命令行模式下可以查询到数据块信息)

select * from default.wqs_test_3;

image-20221213170633422

可以看到新插入的数据新生成了数据块,实际上这里在底层对应新的分区文件片段,那么为什么新插入的数据没有根据日期和之前的数据放入同一个分区文件呢?MergeTree引擎会在插入数据15分钟左右,将同一个分区的各个分区文件片段合并成一整个分区文件。这里也可以手动执行OPTIMIZE 语句手动触发合并。

例如:

optimize table default.wqs_test_3 partition '202102';

image-20221213171543744

image-20221213171603062

接下来查看表 default.wqs_test_3表中的数据,按照相同的分区进行了合并

image-20221213171657711

MergeTree引擎表目录解析

下面我们介绍下MergeTree引擎表 t_mt对应到磁盘的数据目录,为了方便从零开始了解,这里我们删除t_mt表,重新创建t_mt表,并插入数据,执行命令如下:

drop table default.wqs_test_3;
create table default.wqs_test_3 (  
    id UInt8,  
    name String,  
    age UInt8,  
    birthday Date,  
    location String 
) 
ENGINE = MergeTree
order by (id,age) 
partition by toYYYYMM(birthday);
insert into default.wqs_test_3 values 
(1,'张三',18,'2021-06-01','上海'), 
(2,'李四',19,'2021-02-10','北京'), 
(3,'王五',12,'2021-06-01','天津'), 
(1,'马六',10,'2021-06-18','上海'), 
(5,'田七',22,'2021-02-09','广州');

以上创建好表default.wqs_test_3;,当插入数据完成后,在clickhouse节点

/data01/ckk/data/default

路径下(该路径配置可以通过config.properties修改)会生成对应目录wqs_test_3,进入此目录下,可以看到对应的分区目录,如图所示:

image-20221213174900373

以上分区目录也可以在系统表“system.parts”中查询得到:

image-20221213175434466

以上表各列的解释如下:

  • table代表当前表。
  • partition是当前表的分区名称。
  • name是对应到磁盘上数据所在的分区目录片段。例如“202102_2_2_0”中“202102”是分区名称,“2”是数据块的最小编号,“2”是数据块的最大编号,“0”代表该块在MergeTree中第几次合并得到。
  • active代表当前分区片段的状态:1代表激活状态,0代表非激活状态,非激活片段是那些在合并到较大片段之后剩余的源数据片段,损坏的数据片段也表示为非活动状态。非激活片段会在合并后的10分钟左右被删除。

进入到某一个分区目录片段“202102_2_2_0”中,我们可以看到如下目录:

image-20221213182436570

对以上目录的解释如下:

  • checksums.txt:校验文件,使用二进制格式存储。它保存了余下各类文件(primary. idx、count.txt等)的size大小及size的哈希值,用于快速校验文件的完整性和正确性。
  • columns.txt: 存储当前分区所有列信息。使用明文格式存储。

image-20221213182624029

  • count.txt:计数文件,使用明文格式存储。用于记录当前数据分区目录下数据的总行数。

image-20221213182756037

  • primary.idx:一级索引文件(稀疏索引文件),使用二进制格式存储。用于存放稀疏索引,一张MergeTree表只能声明一次一级索引,即通过ORDER BY或者PRIMARY KEY指定字段。借助稀疏索引,在数据查询的时能够排除主键条件范围之外的数据文件,从而有效减少数据扫描范围,加速查询速度。

​ 我们看一下primary.idx文件的内容便于理解稀疏索引文件

​ 创建表如下:

CREATE TABLE default.ansel
(
    `a` Int32,
    `b` Int32,
    `c` Int32,
    INDEX `idx_c` (c) TYPE minmax GRANULARITY 1
)
ENGINE = MergeTree
PARTITION BY a 
ORDER BY b
SETTINGS index_granularity=3, 
index_granularity_bytes = 0 --这里是为了更好的表示稀疏索引格式,平时不建议如此配置;
    • index_granularity` — 索引粒度。索引中相邻的『标记』间的数据行数。默认值8192 。

    • index_granularity_bytes` — 索引粒度,以字节为单位,默认值: 10Mb。如果想要仅按数据行数限制索引粒度, 请设置为0(不建议)。

简单插入点数据

insert into default.ansel(a,b,c) values(3,10,4),(3,9,5),(3,8,6),(3,7,7),(3,6,8),(3,5,9),(3,4,10); 

然后我们查看一下主键索引文件

image-20221214180402134

这里的primary.idx是根据b字段的数据进行固定间隔抽取的。例子中index_granularity=3,所以primary.idx中存的是4、7、10

  • 列.mrk:列字段标记文件,使用二进制格式存储。标记文件中保存了bin文件中数据的偏移量信息,mrk文件与稀疏索引文件对齐,又与bin文件一一对应,所以MergeTree通过标记文件建立了primary.idx稀疏索引与bin数据文件的映射关系,以b.mrk为例

image-20221214180945698

mrk文件分为两列,第一个值对应bin文件中压缩后数据块的偏移量,第二个值对应bin文件解压后数据块的偏移量,单位均为字节。Int32为4字节,索引粒度为3,所以数据块的偏移量是0、12、24。 直观点可以表示成下面规则:

在这里插入图片描述

所以primary.idx和mrk的行是一一对应。

  • 列.bin:数据文件,使用压缩格式存储,默认使用LZ4压缩格式,用于存储某一列的数据。

以b.bin为例

一个压缩数据块由头信息和压缩数据两部分组成。头信息固定使用9位字节表示,具体由1个UInt8(1字节)和2个UInt32(4字节)整型组成,分别代表了使用的压缩算法类型、压缩后的数据大小和压缩前的数据大小。

Checksum:该bin文件的校验值,16字节。

Block:数据块,包含Head和CompressedData。

Head:包含CompressionMethod、CompressedSize、UncompressedSize三部分,其中CompressionMethod类型为UInt8占4字节,包含LZ4(0x82)、ZSTD(0x90)、Multipile(0x91)、Delta(0x92),CompressedSize类型为UInt32占4字节,UncompressedSize类型同样为UInt32占4字节。

CompressedData:压缩数据块,默认最小65535字节/64K,最大1048576字节/1M。

查看bin文件内容可以使用官方的clickhouse-compressor,其余bin文件都可以用这个方法

./clickhouse-compressor --decompress < /data01/ckk/data/default/ansel/3_1_1_0/b.bin | od -An -i -w4

image-20221214184430334

还可以通过clickhouse-compressor查看b.bin的统计信息。

./clickhouse-compressor --stat < /data01/ckk/data/default/ansel/3_1_1_0/b.bin

image-20221214184557986

其中28表示压缩前数据大小,因为从4-10,每个数字占4字节,一共28字节,39表示压缩后数据大小,因为数据量太小,压缩也要有些必要的字节表示一些元信息,所以会比压缩前大,也可以再插入些数据再观察一下,如:

insert into default.ansel(a,b,c) values(4,10,4),(4,9,5),(4,8,6),(4,7,7),(4,6,8),(4,5,9),(4,4,10),(4,4,11),(4,7,12),(4,4,13),(4,8,14),(4,6,15),(4,4,16),(4,6,17);

可以发现,压缩后是小的,如下:

image-20221214185150833

  • partition.dat:用于保存当前分区下分区表达式最终值

    image-20221214185641524

  • minmax_a.idx:用于保存当前分区下分区表达式最终值

image-20221214185759235

  • checksums.txt:校验文件,使用二进制存储,保存了各类文件的size大小和size的哈希值,用于快速校验文件的完整性和正确性。

image-20221215094958178

从中能看到各个column的bin和mrk文件、primary.idx、minmax_a.idx、skp_idx_c.idx、skp_idx_c.mrk的checksum值都会记录在该二进制文件中。

小结

MergeTree引擎表支持分区,索引,修改,并发查询数据,当查询MergeTree表数据时,首先向primary.idx文件中获取对应的索引,根据索引找到【列.mrk2】文件获取对应的数据块偏移量,然后再根据偏移量从【列.bin】文件中读取块数据。

MergeTree引擎表分区

给表设置分区可以在查询过程中跳过不需要的数据目录,提升查询效率。在ClickHouse中并不是所有的表都支持分区,目前只有MergeTree家族系列的表引擎才支持数据分区。

通过前面的学习,我们知道向MergeTree分区表中每次插入数据时,每次都会生成对应的分区片段,不会立刻合并相同分区的数据,需要等待15分钟左右,ClickHouse会自动合并相同的分区片段,并删除合并之前的源数据片段,当然这里我们也可以手动执行OPTIMIZE 语句手动触发合并分区表中的分区片段。通过下面案例来学习分区表中分区片段合并的规则。

示例:

创建表

create table default.login_info(
  id UInt8,
  name String,
  log_time Date
)
engine = MergeTree()
order by (id)
partition by toYYYYMM(log_time);

向表 login_info中插入以下数据

insert into default.login_info values 
(1,'zs','2021-06-01'),
(2,'ls','2021-06-01'),
(3,'ww','2021-07-01'),
(4,'ml','2021-07-01');

查看表login_info

image-20221215103117573

经过以上步骤,在clickhouse节点上查看表login_info数据目录

/data01/ckk/data/default/login_info

image-20221215103242309

继续向表 login_info中插入以下数据

insert into login_info values 
(5,'zs1','2021-06-01'),
(6,'ls1','2021-06-01'),
(7,'ww1','2021-07-01'),
(8,'ml1','2021-07-01');

再次查看表login_info

image-20221215104008148

通过插入数据之后再次查询发现,相同分区的数据展示在不同的数据块中。在clickhouse节点上再次查看表login_info数据目录

/data01/ckk/data/default/login_info

如下图示

image-20221215104221445

“202106_3_3_0”为例,“202006”为分区,“3”代表数据块的最小编号,“3”代表数据块的最大编号,“0”代表合并的第几次(合并树中块的级别)。

手动执行OPTIMIZE 语句手动触发合并分区表中的分区片段:

执行如下命令,手动合并分区片段

optimize table default.login_info partition '202106';

optimize table default.login_info partition '202107';

合并后查看数据

image-20221215104523053

通过合并分区片段之后,在clickhouse节点上再次查看表login_info数据目录

/data01/ckk/data/default/login_info

如下图示:

image-20221215105300394

经过一段时间再次查询当前目录数据,只剩余合并最后的两个分区片段,如下图所示:

image-20221215105338742

MergeTree分区表合并分区规则如下:获取相同分区片段中最小编号和最大编号,组合成新的分区片段,同时修改合并的次数(合并树中块的级别),合并示意图如下:

img

继续向表login_info中插入数据

insert into default.login_info values 
(9,'zs1','2021-06-01'),
(10,'ls1','2021-06-01'),
(11,'ww1','2021-07-01'),
(12,'ml1','2021-07-01');

再次查看表login_info

image-20221215142227868

通过合并分区片段之后,在clickhouse节点上再次查看表login_info数据目录

/data01/ckk/data/default/login_info

如图所示:

image-20221215142448830

再次执行合并分区命令,合并表login_info分区片段:

optimize table default.login_info partition '202106';

optimize table default.login_info partition '202107';

合并后再次查询数据

image-20221215142628946

通过合并分区片段之后,在clickhouse节点上再次查看表login_info数据目录

/data01/ckk/data/default/login_info

如图所示:

image-20221215142754310

经过一段时间再次查询当前目录数据,只剩余合并最后的两个分区片段,如下图所示:

image-20221215143502921

此外,表设置分区字段时,分区健不仅可以指定成时间列,也可以是表中任意列或者列的表达式。下面案例使用表中的地区列当做分区:

例如:

创建表 emp_info ,使用MergeTree分区

create table default.emp_info (
id UInt8,
name String,
age UInt8,
loc String,
salary Decimal32(2)  
)
engine = MergeTree()
order by id
partition by loc;

插入数据

insert into default.emp_info values 
(1,'张三',18,'上海',10.11),
(2,'李四',19,'北京',100.123),
(3,'王五',20,'上海',200.2),
(4,'马六',21,'上海',300.456),
(5,'田七',22,'北京',400.78);

查看表中的数据,可以观察到所有数据都是按照地区合并在一起。

image-20221215144620996

注意:如果按照字符串字段来进行分区,在底层

/data01/ckk/data/default/login_info

目录下对应的表emp_info中的分区片段名称是使用字符串的hashcode+编码的形式来命名。

image-20221215144721533

of
5墨值下载
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文档的来源(墨天轮),文档链接,文档作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

关注
最新上传
暂无内容,敬请期待...
下载排行榜
Top250 周榜 月榜