深度剖析ClickHouse的表引擎
ClickHouse的表引擎
MySQL的数据表有InnoDB和MyISAM存储引擎,不同的存储引擎提供不同的存储机制、索引方式等功能,也可以称之为表类型。在ClickHouse中也有表引擎。
ClickHouse提供了大约28种表引擎,各有各的用途,比如有Log系列用来做小表数据分析,MergeTree系列用来做大数据量分析,而Integration系列则多用于外表数据集成。再考虑复制表Replicated系列,分布式表Distributed等,纷繁复杂。
MergeTree系列表引擎
在所有的表引擎中,最为核心的当属MergeTree系列表引擎,这些表引擎拥有最为强大的性能和最广泛的使用场合。对于非MergeTree系列的其他引擎而言,主要用于特殊用途,场景相对有限。而MergeTree系列表引擎是官方主推的存储引擎,有主键索引、数据分区、数据副本、数据采样、删除和修改等功能,支持几乎所有ClickHouse核心功能。
MergeTree系列表引擎包含:MergeTree、ReplacingMergeTree、SummingMergeTree(汇总求和功能)、AggregatingMergeTree(聚合功能)、CollapsingMergeTree(折叠删除功能)、VersionedCollapsingMergeTree(版本折叠功能)引擎。
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),这里的date_column是一个Date类型的列,分区名的格式会是"YYYYMM"。可选。
-
PRIMARY KEY:指定主键,如果排序字段与主键不一致,可以单独指定主键字段。否则默认主键是排序字段。大部分情况下不需要再专门指定一个 PRIMARY KEY 子句,注意,在MergeTree中主键并不用于去重,而是用于索引,加快查询速度。可选。
另外,如果指定了PRIMARY KEY与排序字段不一致,要保证PRIMARY KEY 指定的主键是ORDER BY 指定字段的前缀,比如:
--允许
... ...
ORDER BY (A,B,C)
PRIMARY KEY A、PRIMARY KEY (A,B)、PRIMARY KEY (A、B)
--报错
... ...
ORDER BY (A,B,C)
PRIMARY KEY B
DB::Exception: Primary key must be a prefix of the sorting key
这种强制约束保障了即便在两者定义不同的情况下,主键仍然是排序键的前缀,不会出现索引与数据顺序混乱的问题。
-
TTL:数据的存活时间。在MergeTree中,可以为某个列字段或整张表设置TTL。当时间到达时,如果是列字段级别的TTL,则会删除这一列的数据;如果是表级别的TTL,则会删除整张表的数据。可选。
示例:
#创建表 t_mt ,使用MergeTree引擎
hadoop10 :) create table t_mt(
:-] id UInt8,
:-] name String,
:-] age UInt8,
:-] birthday Date,
:-] location String
:-] ) engine = MergeTree()
:-] order by (id,age)
:-] partition by toYYYYMM(birthday);
#向表中插入数据
hadoop10 :) insert into t_mt 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','广州');
#查看表中的数据
hadoop10 :) select * from t_mt;
SELECT *
FROM t_mt
┌─id─┬─name─┬─age─┬───birthday─┬─location─┐
│ 1 │ 马六 │ 10 │ 2021-06-18 │ 上海 │
│ 1 │ 张三 │ 18 │ 2021-06-01 │ 上海 │
│ 3 │ 王五 │ 12 │ 2021-06-01 │ 天津 │
└───┴──────┴─────┴──────────┴─────────┘
┌─id─┬─name─┬─age─┬───birthday─┬─location─┐
│ 2 │ 李四 │ 19 │ 2021-02-10 │ 北京 │
│ 5 │ 田七 │ 22 │ 2021-02-09 │ 广州 │
└────┴─────┴─────┴───────────┴────────┘
5 rows in set. Elapsed: 0.006 sec.
继续向表 t_mt中插入以下数据条目:
#向表t_mt中继续插入以下数据
hadoop10 :) insert into t_mt values (1,'赵八',11,'2021-06-08','北京'),
:-] (2,'李九',19,'2021-02-10','天津'),
:-] (3,'郑十',12,'2021-07-01','北京');
#继续查询表 t_mt中的数据
hadoop10 :) select * from t_mt;
SELECT *
FROM t_mt
┌─id─┬─name─┬─age─┬───birthday─┬─location─┐
│ 1 │ 马六 │ 10 │ 2021-06-18 │ 上海 │
│ 1 │ 张三 │ 18 │ 2021-06-01 │ 上海 │
│ 3 │ 王五 │ 12 │ 2021-06-01 │ 天津 │
└────┴──────┴─────┴────────────┴──────────┘
┌─id─┬─name─┬─age─┬───birthday─┬─location─┐
│ 2 │ 李四 │ 19 │ 2021-02-10 │ 北京 │
│ 5 │ 田七 │ 22 │ 2021-02-09 │ 广州 │
└────┴──────┴─────┴────────────┴──────────┘
┌─id─┬─name─┬─age─┬───birthday─┬─location─┐
│ 1 │ 赵八 │ 11 │ 2021-06-08 │ 北京 │
│ 7 │ 郑十 │ 17 │ 2021-06-01 │ 北京 │
└────┴──────┴─────┴────────────┴──────────┘
┌─id─┬─name─┬─age─┬───birthday─┬─location─┐
│ 6 │ 李九 │ 19 │ 2021-02-10 │ 天津 │
└────┴──────┴─────┴────────────┴──────────┘
8 rows in set. Elapsed: 0.008 sec.
可以看到新插入的数据新生成了数据块,实际上这里在底层对应新的分区文件片段,那么为什么新插入的数据没有根据日期和之前的数据放入同一个分区文件呢?MergeTree引擎会在插入数据15分钟左右,将同一个分区的各个分区文件片段合并成一整个分区文件。这里也可以手动执行OPTIMIZE 语句手动触发合并。
示例:
#手动触发表t_mt 的分区合并
hadoop10 :) optimize table t_mt partition '202102';
hadoop10 :) optimize table t_mt partition '202106';
注意:以上optimize 操作,也可以直接写 optimize table t_mt, 每次执行合并一个分区,如果有多个分区需要执行多次。如果想一次合并所有分区,也可以写成 optimize table t_mt final;
#查看表 t_mt表中的数据,按照相同的分区进行了合并。
hadoop10 :) select * from t_mt;
SELECT *
FROM t_mt
┌─id─┬─name─┬─age─┬───birthday─┬─location─┐
│ 1 │ 马六 │ 10 │ 2021-06-18 │ 上海 │
│ 1 │ 赵八 │ 11 │ 2021-06-08 │ 北京 │
│ 1 │ 张三 │ 18 │ 2021-06-01 │ 上海 │
│ 3 │ 王五 │ 12 │ 2021-06-01 │ 天津 │
│ 7 │ 郑十 │ 17 │ 2021-06-01 │ 北京 │
└────┴──────┴─────┴────────────┴──────────┘
┌─id─┬─name─┬─age─┬───birthday─┬─location─┐
│ 2 │ 李四 │ 19 │ 2021-02-10 │ 北京 │
│ 5 │ 田七 │ 22 │ 2021-02-09 │ 广州 │
│ 6 │ 李九 │ 19 │ 2021-02-10 │ 天津 │
└────┴──────┴─────┴────────────┴──────────┘
8 rows in set. Elapsed: 0.004 sec.
注意:MergeTree引擎表中主键并不用于去重,而是用于索引,加快查询速度。
MergeTree引擎表目录解析
下面我们介绍下MergeTree引擎表 t_mt对应到磁盘的数据目录,为了方便从零开始了解,这里我们删除t_mt表,重新创建t_mt表,并插入数据,执行命令如下:
#删除表 t_mt,重新创建表t_mt,并加载数据
hadoop10 :) drop table t_mt;
hadoop10 :)create table t_mt( id UInt8, name String, age UInt8, birthday Date, location String ) engine = MergeTree() order by (id,age) partition by toYYYYMM(birthday);
hadoop10 :) insert into t_mt 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','广州');
以上创建好表t_mt,当插入数据完成后,在clickhouse节点/var/lib/clickhouse/data/default/路径下会生成对应目录“t_mt”,进入此目录下,可以看到对应的分区目录,如图示:
以上分区目录也可以在系统表“system.parts”中查询得到:
#在系统表 system.part中查询表 t_mt的分区信息:
hadoop10 :) select table ,partition ,name ,active from system.parts where table = 't_mt';
以上表各列的解释如下:
-
table代表当前表。
-
partition是当前表的分区名称。
-
name是对应到磁盘上数据所在的分区目录片段。例如“202102_2_2_0”中“202102”是分区名称,“2”是数据块的最小编号,“2”是数据块的最大编号,“0”代表该块在MergeTree中第几次合并得到。
-
active代表当前分区片段的状态:1代表激活状态,0代表非激活状态,非激活片段是那些在合并到较大片段之后剩余的源数据片段,损坏的数据片段也表示为非活动状态。非激活片段会在合并后的10分钟左右被删除。
进入到某一个分区目录片段“202102_2_2_0”中,我们可以看到如下目录:
对以上目录的解释如下:
-
checksums.txt:校验文件,使用二进制格式存储。它保存了余下各类文件(primary. idx、count.txt等)的size大小及size的哈希值,用于快速校验文件的完整性和正确性。
-
columns.txt: 存储当前分区所有列信息。使用明文格式存储。
[root@hadoop10 202102_2_2_0]# cat columns.txt
columns format version: 1
5 columns:
`id` UInt8
`name` String
`age` UInt8
`birthday` Date
`location` String
-
count.txt:计数文件,使用明文格式存储。用于记录当前数据分区目录下数据的总行数。
[root@hadoop10 202102_2_2_0]# cat count.txt
2
-
primary.idx:一级索引文件,使用二进制格式存储。用于存放稀疏索引,一张MergeTree表只能声明一次一级索引,即通过ORDER BY或者PRIMARY KEY指定字段。借助稀疏索引,在数据查询的时能够排除主键条件范围之外的数据文件,从而有效减少数据扫描范围,加速查询速度。
-
列.bin:数据文件,使用压缩格式存储,默认为LZ4压缩格式,用于存储某一列的数据。由于MergeTree采用列式存储,所以每一个列字段都拥有独立的.bin数据文件,并以列字段名称命名。
-
列.mrk2:列字段标记文件,使用二进制格式存储。标记文件中保存了.bin文件中数据的偏移量信息
-
partition.dat与minmax_[Column].idx:如果指定了分区键,则会额外生成partition.dat与minmax索引文件,它们均使用二进制格式存储。partition.dat用于保存当前分区下分区表达式最终生成的值,即分区字段值;而minmax索引用于记录当前分区下分区字段对应原始数据的最小和最大值。比如当使用birthday字段对应的原始数据为2021-02-17、2021-02-23,分区表达式为PARTITION BY toYYYYMM(birthday),即按月分区。partition.dat中保存的值将会是202102,而minmax索引中保存的值将会是2021-02-17、2021-02-23。
ClickHouse MergeTree引擎表支持分区,索引,修改,并发查询数据,当查询MergeTree表数据时,首先向primary.idx文件中获取对应的索引,根据索引找到【列.mrk2】文件获取对应的数据块偏移量,然后再根据偏移量从【列.bin】文件中读取块数据。
MergeTree引擎表设置分区
给表设置分区可以在查询过程中跳过不需要的数据目录,提升查询效率。在ClickHouse中并不是所有的表都支持分区,目前只有MergeTree家族系列的表引擎才支持数据分区。
通过前面的学习,我们知道向MergeTree分区表中每次插入数据时,每次都会生成对应的分区片段,不会立刻合并相同分区的数据,需要等待15分钟左右,ClickHouse会自动合并相同的分区片段,并删除合并之前的源数据片段,当然这里我们也可以手动执行OPTIMIZE 语句手动触发合并分区表中的分区片段。通过下面案例来学习分区表中分区片段合并的规则。
#创建表 login_info ,设置MergeTree引擎
hadoop10 :) create table login_info(
:-] id UInt8,
:-] name String,
:-] log_time Date
:-] ) engine = MergeTree()
:-] order by (id)
:-] partition by toYYYYMM(log_time);
#向表 login_info中插入以下数据
hadoop10 :) insert into 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 中数据
hadoop10 :) select * from login_info;
SELECT *
FROM login_info
┌─id─┬─name─┬───log_time─┐
│ 3 │ ww │ 2021-07-01 │
│ 4 │ ml │ 2021-07-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 1 │ zs │ 2021-06-01 │
│ 2 │ ls │ 2021-06-01 │
└────┴──────┴────────────┘
4 rows in set. Elapsed: 0.008 sec.
经过以上步骤,在clickhouse节点上查看表login_info数据目录/var/lib/clickhouse/data/newdb/login_info,如下图示:
继续向表 login_info中插入以下数据:
#继续向表login_info中插入以下数据
hadoop10 :) 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数据
hadoop10 :) select * from login_info;
SELECT *
FROM login_info
┌─id─┬─name─┬───log_time─┐
│ 3 │ ww │ 2021-07-01 │
│ 4 │ ml │ 2021-07-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 1 │ zs │ 2021-06-01 │
│ 2 │ ls │ 2021-06-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 5 │ zs1 │ 2021-06-01 │
│ 6 │ ls1 │ 2021-06-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 7 │ ww1 │ 2021-07-01 │
│ 8 │ ml1 │ 2021-07-01 │
└────┴──────┴────────────┘
8 rows in set. Elapsed: 0.006 sec.
通过插入数据之后再次查询发现,相同分区的数据展示在不同的数据块中。在clickhouse节点上再次查看表login_info数据目录/var/lib/clickhouse/data/newdb/login_info,如下图示:
“202106_3_3_0”为例,“202006”为分区,“3”代表数据块的最小编号,“3”代表数据块的最大编号,“0”代表合并的第几次(合并树中块的级别)。
手动执行OPTIMIZE 语句手动触发合并分区表中的分区片段:
#执行如下命令,手动合并分区片段
hadoop10 :) optimize table login_info partition '202106' ;
hadoop10 :) optimize table login_info partition '202107' ;
#查看表 login_info中的数据:
hadoop10 :) select * from login_info;
SELECT *
FROM login_info
┌─id─┬─name─┬───log_time─┐
│ 3 │ ww │ 2021-07-01 │
│ 4 │ ml │ 2021-07-01 │
│ 7 │ ww1 │ 2021-07-01 │
│ 8 │ ml1 │ 2021-07-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 1 │ zs │ 2021-06-01 │
│ 2 │ ls │ 2021-06-01 │
│ 5 │ zs1 │ 2021-06-01 │
│ 6 │ ls1 │ 2021-06-01 │
└────┴──────┴────────────┘
8 rows in set. Elapsed: 0.006 sec.
通过合并分区片段之后,在clickhouse节点上再次查看表login_info数据目录
/var/lib/clickhouse/data/newdb/login_info,如下图示:
经过一段时间再次查询当前目录数据,只剩余合并最后的两个分区片段,如下图所示:
MergeTree分区表合并分区规则如下:获取相同分区片段中最小编号和最大编号,组合成新的分区片段,同时修改合并的次数(合并树中块的级别),合并示意图如下:
继续向表login_info中插入数据:
#继续向表login_info中插入以下数据
hadoop10 :) insert into 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数据
hadoop10 :) select * from login_info;
SELECT *
FROM login_info
┌─id─┬─name─┬───log_time─┐
│ 3 │ ww │ 2021-07-01 │
│ 4 │ ml │ 2021-07-01 │
│ 7 │ ww1 │ 2021-07-01 │
│ 8 │ ml1 │ 2021-07-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 11 │ ww1 │ 2021-07-01 │
│ 12 │ ml1 │ 2021-07-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 1 │ zs │ 2021-06-01 │
│ 2 │ ls │ 2021-06-01 │
│ 5 │ zs1 │ 2021-06-01 │
│ 6 │ ls1 │ 2021-06-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 9 │ zs1 │ 2021-06-01 │
│ 10 │ ls1 │ 2021-06-01 │
└────┴──────┴────────────┘
12 rows in set. Elapsed: 0.006 sec
在clickhouse节点上再次查看表login_info数据目录/var/lib/clickhouse/data/newdb/login_info,如下图示:
再次执行合并分区命令,合并表login_info分区片段:
#执行如下命令,手动合并分区片段
hadoop10 :) optimize table login_info partition '202106' ;
hadoop10 :) optimize table login_info partition '202107' ;
#查看表 login_info中的数据:
hadoop10 :) select * from login_info;
SELECT *
FROM login_info
┌─id─┬─name─┬───log_time─┐
│ 3 │ ww │ 2021-07-01 │
│ 4 │ ml │ 2021-07-01 │
│ 7 │ ww1 │ 2021-07-01 │
│ 8 │ ml1 │ 2021-07-01 │
│ 11 │ ww1 │ 2021-07-01 │
│ 12 │ ml1 │ 2021-07-01 │
└────┴──────┴────────────┘
┌─id─┬─name─┬───log_time─┐
│ 1 │ zs │ 2021-06-01 │
│ 2 │ ls │ 2021-06-01 │
│ 5 │ zs1 │ 2021-06-01 │
│ 6 │ ls1 │ 2021-06-01 │
│ 9 │ zs1 │ 2021-06-01 │
│ 10 │ ls1 │ 2021-06-01 │
└────┴──────┴────────────┘
12 rows in set. Elapsed: 0.008 sec.
通过合并分区片段之后,在clickhouse节点上再次查看表login_info数据目录/var/lib/clickhouse/data/newdb/login_info,如下图示:
经过一段时间再次查询当前目录数据,只剩余合并最后的两个分区片段,如下图所示:
ReplacingMergeTree
以上MergeTree不能对相同主键的数据进行去重,ClickHouse提供了ReplacingMergeTree引擎,可以针对同分区内相同主键的数据进行去重,它能够在合并分区时删除重复的数据。值得注意的是,ReplacingMergeTree只是在一定程度上解决了数据重复问题,由于自动分区合并机制在后台定时执行,所以并不能完全保障数据不重复。ReplacingMergeTree 适用于在后台清除重复的数据以节省空间。
-
ReplaceingMergeTree建表语句:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = ReplacingMergeTree([ver])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
以上建表语句的解释如下:
n [ver] :可选参数,指定列的版本,可以是UInt*、Date或者DateTime类型的字段作为版本号。该参数决定了数据去重的方式。当没有指定[ver]时,保留最后插入的数据,也就是最新的数据;如果指定了具体的[ver]列,则保留最大版本数据。
使用ReplacingMergeTree是需要注意以下几点:
-
如何判断数据重复
ReplacingMergeTree在去除重复数据时,是以ORDERBY排序键为基准的,而不是PRIMARY KEY。
-
何时删除重复数据
在执行分区合并时,会触发删除重复数据。optimize的合并操作是在后台执行的,无法预测具体执行时间点,除非是手动执行。
-
不同分区的重复数据不会被去重
ReplacingMergeTree是以分区为单位删除重复数据的。只有在相同的数据分区内重复的数据才可以被删除,而不同数据分区之间的重复数据依然不能被剔除。
-
数据去重的策略是什么
如果没有设置[ver]版本号,则保留同一组重复数据中的最新插入的数据;如果设置了[ver]版本号,则保留同一组重复数据中ver字段取值最大的那一行。
-
optimize命令使用
一般在数据量比较大的情况,尽量不要使用该命令。因为在海量数据场景下,执行optimize要消耗大量时间。
示例:
测试去重按照Order by 字段进行去重,而不是按照primary 主键字段进行去重。
#创建表 t_replacing_mt ,使用ReplacingMergeTree引擎
hadoop10 :) create table t_replacing_mt(
:-] id UInt8,
:-] name String,
:-] age UInt8,
:-] gender String
:-] ) engine = ReplacingMergeTree()
:-] order by (id,age)
:-] primary key id
:-] partition by gender;
#向表 t_replacing_mt 中插入以下数据:
hadoop10 :) insert into t_replacing_mt values (1,'张三',18,'男'),
:-] (2,'李四',19,'女'),
:-] (3,'王五',20,'男');
#查询表 t_replacing_mt 中的数据:
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└───┴────┴────┴──────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└───┴────┴────┴──────┘
#向表 t_replacing_mt 中插入id 为1的一行数据
hadoop10 :) insert into t_replacing_mt values (1,'张三',10,'男');
#查询表 t_replacing_mt 数据:
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 10 │ 男 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└────┴──────┴─────┴────────┘
#执行 optimize命令手动合并分区数据
hadoop10 :) optimize table t_replacing_mt;
#查询表 t_replacing_mt 数据,发现没有按照primary key 去重。
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 10 │ 男 │
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└────┴──────┴─────┴────────┘
#再次向表 t_replacing_mt 插入数据:
hadoop10 :) insert into t_replacing_mt values (1,'张三三',18,'男');
#查询表 t_replacing_mt 数据
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name───┬─age─┬─gender─┐
│ 1 │ 张三三 │ 18 │ 男 │
└───┴──────┴────┴──────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└───┴────┴────┴──────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 10 │ 男 │
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└───┴─────┴───┴──────┘
#再次执行 optimize命令手动合并分区数据
hadoop10 :) optimize table t_replacing_mt;
#查询表 t_replacing_mt 数据
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└───┴────┴────┴──────┘
┌─id─┬─name───┬─age─┬─gender─┐
│ 1 │ 张三 │ 10 │ 男 │
│ 1 │ 张三三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└───┴──────┴────┴─────┘
注意:通过以上测试发现ClickHouse ReplacingMergeTree中去除重复数据时,是以ORDER BY排序键为基准的,而不是PRIMARY KEY。
测试不指定[ver]列时,插入相同排序字段的数据,保留最新一条数据。
#删除表 t_replacing_mt 重建,使用ReplacingMergeTree引擎
hadoop10 :) create table t_replacing_mt(
:-] id UInt8,
:-] name String,
:-] age UInt8,
:-] gender String
:-] ) engine = ReplacingMergeTree()
:-] order by id
:-] primary key id
:-] partition by gender;
#向表 t_replacing_mt 中插入以下数据
hadoop10 :) insert into t_replacing_mt values (1,'张三',18,'男'),
:-] (2,'李四',19,'女'),
:-] (3,'王五',20,'男');
#查询表 t_replacing_mt 中的数据
hadoop10 :) select * from t_replacing_mt ;
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└────┴──────┴─────┴────────┘
#向表 t_replacing_mt 中插入排序字段相同的一行数据
hadoop10 :) insert into t_replacing_mt values (1,'张三',10,'男');
#查询表 t_replacing_mt 中的数据
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 10 │ 男 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└────┴──────┴─────┴────────┘
#执行 optimize命令手动合并分区数据
hadoop10 :) optimize table t_replacing_mt;
#查询表 t_replacing_mt 中的数据
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 10 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└────┴──────┴─────┴────────┘
注意:通过以上测试可以发现,ClickHouse ReplacingMergeTree中不指定[ver]列时,当插入排序字段相同的数据时,保留最新一条数据。
测试指定[ver]列时,插入相同排序字段的数据,保留当前[ver]列最大值。
#删除表 t_replacing_mt 重新创建,使用ReplacingMergeTree引擎,指定[ver]
hadoop10 :) create table t_replacing_mt(
:-] id UInt8,
:-] name String,
:-] age UInt8,
:-] gender String
:-] ) engine = ReplacingMergeTree(age)
:-] order by id
:-] primary key id
:-] partition by gender;
#向表 t_replacing_mt 中插入数据:
hadoop10 :) insert into t_replacing_mt values (1,'张三',18,'男'),
:-] (2,'李四',19,'女'),
:-] (3,'王五',20,'男');
#查询表 t_replacing_mt中数据:
hadoop10 :) select * from t_replacing_mt ;
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└────┴──────┴─────┴────────┘
#向表 t_replacing_mt 中插入排序字段相同的一行数据
hadoop10 :) insert into t_replacing_mt values (1,'张三',10,'男');
#查看表 t_replacing_mt中的数据
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 10 │ 男 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└────┴──────┴─────┴────────┘
#对表 t_replacing_mt中的数据执行手动分区合并
hadoop10 :) optimize table t_replacing_mt;
#查看表 t_replacing_mt中的数据
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name─┬─age─┬─gender─┐
│ 2 │ 李四 │ 19 │ 女 │
└────┴──────┴─────┴────────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└────┴──────┴─────┴────────┘
注意:通过以上测试可以发现,在ClickHouse中创建ReplacingMergeTree时,如果指定了[ver]列,当存在Order by字段重复时,会保留ver列最大值对应的行。
测试不同分区中有相同的Order by 字段时,不去重。
#删除表 t_replacing_mt ,重新创建
hadoop10 :) create table t_replacing_mt(
:-] id UInt8,
:-] name String,
:-] age UInt8,
:-] gender String
:-] ) engine = ReplacingMergeTree()
:-] order by id
:-] primary key id
:-] partition by gender;
#向表 t_replacing_mt 中插入以下数据:
hadoop10 :) insert into t_replacing_mt values (1,'张三',18,'男'),
:-] (2,'李四',19,'女'),
:-] (3,'王五',20,'男');
#再次向表 t_replacing_mt 中插入以下数据:
hadoop10 :) insert into t_replacing_mt values (1,'张三三',10,'女');
#对表 t_replacing_mt中的数据执行手动分区合并
hadoop10 :) optimize table t_replacing_mt;
#查看表中的数据
hadoop10 :) select * from t_replacing_mt;
┌─id─┬─name───┬─age─┬─gender─┐
│ 1 │ 张三三 │ 10 │ 女 │
│ 2 │ 李四 │ 19 │ 女 │
└───┴──────┴────┴─────┘
┌─id─┬─name─┬─age─┬─gender─┐
│ 1 │ 张三 │ 18 │ 男 │
│ 3 │ 王五 │ 20 │ 男 │
└───┴────┴────┴─────┘
注意:通过以上测试可以发现,在ClickHouse中创建ReplacingMergeTree时,不同分区中相同的Order by 字段不会去重。
SummingMergeTree
该引擎继承了MergeTree引擎,当合并 SummingMergeTree 表的数据片段时,ClickHouse 会把所有具有相同主键的行合并为一行,该行包含了被合并的行中具有数值数据类型的列的汇总值,即如果存在重复的数据,会对这些重复的数据进行合并成一条数据,类似于group by的效果,可以显著减少存储空间并加快数据查询速度。
如果用户只需要查询数据的汇总结果,不关心明细数据,并且数据的汇总条件是预先明确的,即GROUP BY的分组字段是确定的,可以使用该表引擎。
SummingMergeTree建表语句:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = SummingMergeTree([columns])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
对以上建表语句的解释如下:
-
[columns]: 将要被汇总的列,或者多个列,多个列需要写在元组中。可选参数。所选的列必须是数值类型,并且不可位于主键中。如果没有指定 [columns],ClickHouse 会把所有不在主键中的数值类型的列都进行汇总。
使用SummingMergeTree注意以下几点:
-
SummingMergeTree是根据什么对两条数据进行合并的
用ORBER BY排序键作为聚合数据的条件Key。即如果排序key是相同的,则会合并成一条数据,并对指定的合并字段进行聚合。
-
仅对分区内的相同排序key的数据行进行合并
以数据分区为单位来聚合数据。当分区合并时,同一数据分区内聚合Key相同的数据会被合并汇总,而不同分区之间的数据则不会被汇总。
-
如果没有指定聚合字段,会怎么聚合
如果没有指定聚合字段,则会按照非主键的数值类型字段进行聚合。
-
对于非汇总字段的数据,该保留哪一条
如果两行数据除了排序字段相同,其他的非聚合字段不相同,那么在聚合发生时,会保留最初的那条数据,新插入的数据对应的那个字段值会被舍弃。
示例:
测试不指定聚合字段同时测试不同分区内,相同排序key数据不会被合并
#创建表 t_summing_mt ,使用SummingMergeTree表引擎
hadoop10 :) create table t_summing_mt(
:-] id UInt8,
:-] name String,
:-] age UInt8,
:-] loc String,
:-] dept String,
:-] workdays UInt8,
:-] salary Decimal32(2)
:-] ) engine = SummingMergeTree()
:-] order by (id,age)
:-] primary key id
:-] partition by loc;
#向表 t_summing_mt 中插入以下数据
hadoop10 :) insert into t_summing_mt values (1,'张三',18,'北京','大数据',24,10000),
:-] (2,'李四',19,'上海','java',22,8000),
:-] (3,'王五',20,'北京','java',26,12000);
#查看表 t_summing_mt 中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 24 │ 10000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
#向表 t_summing_mt 中插入一条排序键相同的数据
hadoop10 :) insert into t_summing_mt values (1,'马六',18,'北京','前端',27,15000);
#查看表 t_summing_mt中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 24 │ 10000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬───salary─┐
│ 1 │ 马六 │ 18 │ 北京 │ 前端 │ 27 │ 15000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
#手动执行optimize 命令触发合并相同分区数据
hadoop10 :) optimize table t_summing_mt;
#查看表 t_summing_mt 中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 51 │ 25000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
注意:我们可以看到当不指定 聚合字段时,有相同排序字段行进行聚合时,会将数值类型的字段进行聚合合并。
#继续向表 t_summing_mt中插入以下数据:
hadoop10 :) insert into t_summing_mt values (1,'张三',18,'南京','java',18,12000);
#查看表 t_summing_mt中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 南京 │ java │ 18 │ 12000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 51 │ 25000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
#手动指定optimize 命令合并相同排序key的数据
hadoop10 :) optimize table t_summing_mt;
#查看表 t_summing_mt中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 南京 │ java │ 18 │ 12000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 51 │ 25000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
注意:不同分区内相同的排序key的数据不能被合并
-
测试指定一个聚合字段
#删除表 t_summing_mt,重新创建表 t_summing_mt ,使用SummingMergeTree引擎
hadoop10 :) create table t_summing_mt(
:-] id UInt8,
:-] name String,
:-] age UInt8,
:-] loc String,
:-] dept String,
:-] workdays UInt8,
:-] salary Decimal32(2)
:-] ) engine = SummingMergeTree(salary)
:-] order by (id,age)
:-] primary key id
:-] partition by loc;
#向表 t_summing_mt 中插入以下数据
hadoop10 :) insert into t_summing_mt values (1,'张三',18,'北京','大数据',24,10000),
:-] (2,'李四',19,'上海','java',22,8000),
:-] (3,'王五',20,'北京','java',26,12000);
#查看表 t_summing_mt 中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 24 │ 10000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
#向表 t_summing_mt 中插入一条排序键相同的数据
hadoop10 :) insert into t_summing_mt values (1,'马六',18,'北京','前端',27,15000);
#查看表 t_summing_mt中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 24 │ 10000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬───salary─┐
│ 1 │ 马六 │ 18 │ 北京 │ 前端 │ 27 │ 15000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
#手动执行optimize 命令触发合并相同分区数据
hadoop10 :) optimize table t_summing_mt;
#查看表 t_summing_mt 中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 24 │ 25000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
注意:我们可以看到当指定一个聚合字段时,有相同排序字段行进行聚合时,会按照这个数值字段进行合并,其他的保留最开始一条数据的信息。
-
测试指定多个聚合字段
#删除表 t_summing_mt,重新创建表 t_summing_mt ,使用SummingMergeTree引擎
hadoop10 :) create table t_summing_mt(
:-] id UInt8,
:-] name String,
:-] age UInt8,
:-] loc String,
:-] dept String,
:-] workdays UInt8,
:-] salary Decimal32(2)
:-] ) engine = SummingMergeTree((salary,workdays))
:-] order by (id,age)
:-] primary key id
:-] partition by loc;
#向表 t_summing_mt 中插入以下数据
hadoop10 :) insert into t_summing_mt values (1,'张三',18,'北京','大数据',24,10000),
:-] (2,'李四',19,'上海','java',22,8000),
:-] (3,'王五',20,'北京','java',26,12000);
#查看表 t_summing_mt 中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 24 │ 10000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
#向表 t_summing_mt 中插入一条排序键相同的数据
hadoop10 :) insert into t_summing_mt values (1,'马六',18,'北京','前端',27,15000);
#查看表 t_summing_mt中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 24 │ 10000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬───salary─┐
│ 1 │ 马六 │ 18 │ 北京 │ 前端 │ 27 │ 15000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
#手动执行optimize 命令触发合并相同分区数据
hadoop10 :) optimize table t_summing_mt;
#查看表 t_summing_mt 中的数据
hadoop10 :) select * from t_summing_mt;
┌─id─┬─name─┬─age─┬─loc──┬─dept───┬─workdays─┬───salary─┐
│ 1 │ 张三 │ 18 │ 北京 │ 大数据 │ 51 │ 25000.00 │
│ 3 │ 王五 │ 20 │ 北京 │ java │ 26 │ 12000.00 │
└────┴──────┴─────┴──────┴────────┴──────────┴──────────┘
┌─id─┬─name─┬─age─┬─loc──┬─dept─┬─workdays─┬──salary─┐
│ 2 │ 李四 │ 19 │ 上海 │ java │ 22 │ 8000.00 │
└────┴──────┴─────┴──────┴──────┴──────────┴─────────┘
注意:我们可以看到当指定多个聚合字段时,有相同排序字段行进行聚合时,会按照指定的多个数值字段进行合并,其他的保留最开始一条数据的信息。
技术总结
ClickHouse 引擎决定数据存储、查询及扩展方式,核心分 **表引擎** 与 **数据库引擎**,表引擎为核心,决定单表存储逻辑。
表引擎按功能分:
1. **日志引擎**(Log/TinyLog/StripeLog):适用于小批量日志,无索引,写入快但查询效率低,仅支持顺序扫描;
2. **合并树引擎**(MergeTree 家族):企业级核心,支持分区、排序、索引,含基础 MergeTree(通用)、ReplacingMergeTree(去重)、SummingMergeTree(预聚合)等,适配海量数据高效查询;
3. **分布式引擎**(Distributed):代理引擎,本身不存数据,用于分布式集群,实现跨节点数据访问与计算;
4. **特殊引擎**(Kafka/MySQL/ODBC):对接外部系统,支持直接读写 Kafka 数据、关联 MySQL 表等。
数据库引擎多为适配特定场景,如 Ordinary(默认)、Dictionary(字典库)等。选择需结合业务:实时高并发查用 MergeTree 家族,日志存储用 Log 引擎,分布式场景配 Distributed 引擎 。
- 点赞
- 收藏
- 关注作者
评论(0)