ClickHouse提供了多种不同的表引擎,表引擎可以简单理解为不同类型的表。
表引擎(即表的类型)决定了:
- 数据的存储方式和位置,写到哪里以及从哪里读取数据
- 支持哪些查询以及如何支持
- 并发数据访问
- 索引的使用(如果存在)
- 是否可以执行多线程请求
- 数据复制参数
下面介绍其中常用的几种,对其他引擎有兴趣的可以去查阅官方文档:https://clickhouse.tech/docs/zh/engines/table-engines/
1 日志引擎
1.1 TinyLog引擎
最简单的表引擎,用于将数据存储在磁盘上。每列都存储在单独的压缩文件中,写入时,数据将附加到文件末尾。
该引擎没有并发控制
- 如果同时从表中读取和写入数据,则读取操作将抛出异常;
- 如果同时写入多个查询中的表,则数据将被破坏。
这种表引擎的典型用法是
write-once
:首先只写入一次数据,然后根据需要多次读取。此引擎适用于相对较小的表(建议最多1,000,000行)。如果有许多小表,则使用此表引擎是适合的,因为它比需要打开的文件更少。当拥有大量小表时,可能会导致性能低下。不支持索引。
案例:创建一个TinyLog引擎的表并插入一条数据
create table user (id UInt16, name String) ENGINE=TinyLog;
insert into user (id, name) values (1, 'zhangsan');
此时我们到保存数据的目录
/var/lib/clickhouse/data/default/user
中可以看到如下目录结构:
id.bin
name.bin
sizes.json
id.bin 和 name.bin 是压缩过的对应的列的数据,sizes.json 中记录了每个 *.bin 文件的大小。
2 数据库引擎
ClickHouse提供了
本机
、
MySQL
和
Lazy
这3种数据库引擎,但在默认情况下仅使用其本机数据库引擎,该引擎提供可配置的表引擎(MergeTree、Log和Intergation)和SQL方言(完整的SQL解析器,即递归下降解析器;数据格式解析器,即快速流解析器)。还可以使用MySQL和Lazy。
2.1 MySQL引擎
MySQL引擎用于将远程的MySQL服务器中的表映射到ClickHouse中
,并允许您对表进行
INSERT
和
SELECT
查询,以方便您
在ClickHouse与MySQL之间进行数据交换
。
MySQL数据库引擎会将对其的查询转换为MySQL语法并发送到MySQL服务器中,因此您可以执行诸如
SHOW TABLES
或
SHOW CREATE TABLE
之类的操作。
但您无法对其执行以下操作:
- RENAME
- CREATE TABLE
- ALTER
语法结构:
-- 创建Mysql引擎的语法
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MySQL('host:port', ['database' | database], 'user', 'password')
MySQL数据库引擎参数:
- host:port— 链接的MySQL地址。
- database— 链接的MySQL数据库。
- user— 链接的MySQL用户。
- password— 链接的MySQL用户密码。
使用示例:
-
在MySQL中创建表:
mysql> USE test; Database changed mysql> CREATE TABLE `mysql_table` ( -> `int_id` INT NOT NULL AUTO_INCREMENT, -> `float` FLOAT NOT NULL, -> PRIMARY KEY (`int_id`)); Query OK, 0 rows affected (0,09 sec) mysql> insert into mysql_table (`int_id`, `float`) VALUES (1,2); Query OK, 1 row affected (0,00 sec) mysql> select * from mysql_table; +--------+-------+ | int_id | value | +--------+-------+ | 1 | 2 | +--------+-------+ 1 row in set (0,00 sec)
-
在ClickHouse中创建MySQL类型的数据库,同时与MySQL服务器交换数据:
CREATE DATABASE mysql_db ENGINE = MySQL('www.yourhose.com:3306', 'test', 'root', '123456') SHOW DATABASES; ┌─name─────┐ │ default │ │ mysql_db │ │ system │ └──────────┘ SHOW TABLES FROM mysql_db ┌─name─────────┐ │ mysql_table │ └──────────────┘ SELECT * FROM mysql_db.mysql_table ┌─int_id─┬─value─┐ │ 1 │ 2 │ └────────┴───────┘ INSERT INTO mysql_db.mysql_table VALUES (3,4) SELECT * FROM mysql_db.mysql_table ┌─int_id─┬─value─┐ │ 1 │ 2 │ │ 3 │ 4 │ └──────┴─────┘
3 MergeTree系列引擎(重点)
MergeTree(合并树)
系列引擎是ClickHouse中最强大的表引擎,是
官方主推的存储引擎
,几乎支持ClickHouse所有的核心功能。
该系列引擎主要用于海量数据分析的场景,支持对表数据进行
分区、复制、采样、存储有序、主键索引、稀疏索引和数据TTL
等特性。
MergeTree系列引擎的基本理念是
当有大量数据要插入到表中时,需要高效地一批一批的写入数据片段,并希望这些数据片段在后台按照一定规则合并,这种方法比插入期间连续重写存储中的数据效率更高
。
简而言之就是具有批量数据快速插入和后台并发处理的优势。
MergeTree系列引擎支持ClickHouse所有的SQL语法,但还是有一些SQL语法和MySQL并不太一样。
MergeTree系列引擎包括:
- ReplacingMergeTree
- SummingMergeTree
- AggregatingMergeTree
- CollapsingMergeTree
- VersionedCollapsingMergeTree
3.1 MergeTree
MergeTree引擎的表的允许插入主键重复的数据,
主键主要作用是生成主键索引来提升查询效率,而不是用来保持记录主键唯一
-
创建MergeTree表的说明
-- 创建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() [PARTITION BY expr] [ORDER BY expr] [PRIMARY KEY expr] [SAMPLE BY expr] [TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...] [SETTINGS name=value, ...]
子句说明 使用方式 ENGINE ENGINE = MergeTree() –说明:该引擎不需要参数。 PARTITION BY 字段名称 PARTITION by to YYYYMM(cdt) ORDER BY 字段名称(可以是元组) ORDER BY cdt或ORDER BY (age,gender) PRIMARY KEY 字段名称 PRIMARY KEY age SAMPLE BY 字段名称 SAMPLE BY intHash64(userId) TTL Date字段或DateTime字段 TTL cdt + INTERVAL 1 DAY SETTINGS index_granularity=8192 说明:索引粒度。即索引中相邻”标记”间的数据行数。设为 8192 可以适用大部分场景。 SETTINGS index_granularity_bytes= 说明:设置数据粒度的最大大小(单位/字节),默认10MB。从大行(数十和数百MB)的表中select数据时,此设置可提高ClickHouse的提高select性能。 SETTINGS enable_mixed_granularity_parts 说明:启用或禁用过渡。 SETTINGS use_minimalistic_part_header_in_zookeeper 说明:在ZK中存储数据部分标题,0是关闭,1是存储的少量数据。 SETTINGS min_merge_bytes_to_use_direct_io 说明:使用对存储磁盘的直接I / O访问所需的最小合并操作数据量。合并数据部分时,ClickHouse会计算要合并的所有数据的总存储量。如果卷超过min_merge_bytes_to_use_direct_io字节,ClickHouse将使用直接I/O接口(O_DIRECT选项)读取数据并将数据写入存储磁盘。如果为min_merge_bytes_to_use_direct_io = 0,则直接I / O被禁用。
默认值:10 * 1024 * 1024 * 1024字节。SETTINGS merge_with_ttl_timeout 说明:与TTL合并之前的最小延迟(单位/秒),默认86400。 SETTINGS write_final_mark 说明:启用或禁用在数据部分末尾写入最终索引标记,默认1。建议不关闭此设置。 SETTINGS storage_policy 说明:存储策略。 -
创建MergeTree引擎的表
创建MergeTree引擎表有两种方式,
一种是集群表,一种是本地表
。创建使用MergeTree引擎的集群表test.tbl_testmergetree_users_all,集群表一般都携带_all后缀,而且必须所有节点都存在test数据库,这样所有节点的test库中都有tbl_testmergetree_users_all表。
CREATE TABLE test.tbl_test_mergetree_users_all ON cluster 'ch_cluster'( id UInt64, email String, username String, gender UInt8, birthday Date, mobile FixedString(13), pwd String, regDT DateTime, lastLoginDT DateTime, lastLoginIP String ) ENGINE=MergeTree() partition by toYYYYMMDD(regDT) order by id settings index_granularity=8192;
创建使用MergeTree引擎的
本地表
test.tbl_test_mergetree_usersCREATE TABLE tbl_test_mergetree_users( id UInt64, email String, username String, gender UInt8, birthday DATE, mobile FixedString(13), pwd String, regDT DateTime, lastLoginDT DateTime, lastLoginIP String ) ENGINE=MergeTree() partition by toYYYYMMDD(regDT) order by id settings index_granularity=8192;
-
插入数据到MergeTree引擎的表
1、测试数据集
values (1,'wcfr817e@yeah.net','张三',2,'1992-05-31','13306834911','7f930f90eb6604e837db06908cc95149','2008-08-06 11:48:12','2015-05-08 10:51:41','106.83.54.165'),(2,'xuwcbev9y@ask.com','李四',1,'1983-10-11','15302753472','7f930f90eb6604e837db06908cc95149','2008-08-10 05:37:32','2014-07-28 23:43:04','121.77.119.233'),(3,'mgaqfew@126.com','王五',1,'1970-11-22','15200570030','96802a851b4a7295fb09122b9aa79c18','2008-08-10 11:37:55','2014-07-22 23:45:47','171.12.206.122');
2、插入数据到集群表test.tbl_test_mergetree_users_all
使用SQL语句
insert into test.tbl_test_mergetree_users_all(id, email, username, gender, birthday, mobile, pwd, regDT, lastLoginDT, lastLoginIP) values (1,'wcfr817e@yeah.net','张三',2,'1992-05-31','13306834911','7f930f90eb6604e837db06908cc95149','2008-08-06 11:48:12','2015-05-08 10:51:41','106.83.54.165'),(2,'xuwcbev9y@ask.com','李四',1,'1983-10-11','15302753472','7f930f90eb6604e837db06908cc95149','2008-08-10 05:37:32','2014-07-28 23:43:04','121.77.119.233'),(3,'mgaqfew@126.com','王五',1,'1970-11-22','15200570030','96802a851b4a7295fb09122b9aa79c18','2008-08-10 11:37:55','2014-07-22 23:45:47','171.12.206.122');
3、插入数据到本地表test.tbl_test_mergetree_users
使用SQL语句
insert into tbl_test_mergetree_users(id, email, username, gender, birthday, mobile, pwd, regDT, lastLoginDT, lastLoginIP) values (1,'wcfr817e@yeah.net','张三',2,'1992-05-31','13306834911','7f930f90eb6604e837db06908cc95149','2008-08-06 11:48:12','2015-05-08 10:51:41','106.83.54.165'),(2,'xuwcbev9y@ask.com','李四',1,'1983-10-11','15302753472','7f930f90eb6604e837db06908cc95149','2008-08-10 05:37:32','2014-07-28 23:43:04','121.77.119.233'),(3,'mgaqfew@126.com','王五',1,'1970-11-22','15200570030','96802a851b4a7295fb09122b9aa79c18','2008-08-10 11:37:55','2014-07-22 23:45:47','171.12.206.122');
-
删除MergeTree引擎的表
1、删除集群中所有节点的tbl_test_mergetree_users_all表
drop table test.tbl_test_mergetree_users_all on cluster 'ch_cluster';
2、删除tbl_test_mergetree_users本地表
drop table tbl_test_mergetree_users;
3.2 ReplacingMergeTree
为了解决MergeTree相同主键无法去重的问题
,ClickHouse提供了
ReplacingMergeTree
引擎,用来对主键重复的数据进行去重。
删除重复数据可以使用
optimize
命令手动执行,这个合并操作是在后台运行的,且无法预测具体的执行时间。
在使用optimize命令执行合并时,如果表数据量过大,会导致耗时很长,此时表将是不可用的,因为optimize会通过读取和写入大量数据来完成合并操作。
ReplacingMergeTree适合在后台清除重复数据以节省空间,但不能保证不存在重复数据。在没有彻底optimize之前,可能无法达到主键去重的效果,比如部分数据已经被去重,而另外一部分数据仍旧存在主键重复的情况。在分布式场景下,相同主键的数据可能被分片到不同节点上,不同分片间无法去重。
ReplacingMergeTree更多的被用于确保数据最终被去重,而无法保证查询过程中主键不重复
。
-
创建ReplacingMergeTree表的说明
1、创建ReplacingMergeTree引擎表的语法
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] [PRIMARY KEY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
子句说明 使用方式 ver参数 ReplacingMergeTree([ver])中的ver参数是可选的,指带有版本的列,这个列允许使用UInt*、Date或DateTime类型。ReplacingMergeTree在合并时会把具有相同主键的所有行仅保留一个。如果不指定ver参数则保留最后一次插入的数据。 -
创建ReplacingMergeTree引擎的表
1、创建ReplacingMergeTree引擎的本地表tbl_test_replacing_mergetree_users
CREATE TABLE tbl_test_replacingmergetree_users ( id UInt64, email String, username String, gender UInt8, birthday Date, mobile FixedString(13), pwd String, regDT DateTime, lastLoginDT DateTime, lastLoginIP String ) ENGINE=ReplacingMergeTree(id) partition by toYYYYMMDD(regDT) order by id settings index_granularity=8192;
-
插入数据到ReplacingMergeTree引擎的表
插入数据到表tbl_test_replacingmergetree_users
使用SQL语句插入数据:
insert into tbl_test_replacingmergetree_users select * from tbl_test_mergetree_users where id<=5;
插入重复数据(使用lastLoginDT来区分数据插入的先后顺序):
insert into tbl_test_replacingmergetree_users(id,email,username,gender,birthday,mobile,pwd,regDT,lastLoginIP,lastLoginDT) select id,email,username,gender,birthday,mobile,pwd,regDT,lastLoginIP,now() as lastLoginDT from tbl_test_mergetree_users where id<=3;
查询表中全量数据:
select * from tbl_test_replacingmergetree_users order by id,lastLoginDT;
现在使用optimize命令对表中主键id字段的重复数据执行合并操作。
optimize table tbl_test_replacingmergetree_users final;
-
删除表
drop table tbl_test_replacingmergetree_users;
3.3 SummingMergeTree
ClickHouse通过SummingMergeTree来支持对主键列进行预聚合。
在后台合并时,会将主键相同的多行进行sum求和,然后使用一行数据取而代之,从而大幅度降低存储空间占用,提升聚合计算性能。
ClickHouse只在后台
Compaction
时才会进行数据的预先聚合,而compaction的执行时机无法预测,所以可能会存在一部分数据已经被预先聚合,但仍有一部分数据尚未被聚合的情况。
因此在执行聚合计算时,SQL中仍需要使用GROUP BY子句来保证sum的准确。
在预聚合时,ClickHouse会对主键列以外的其他所有列进行预聚合。但这些列必须是数值类型才会计算sum(当sum结果为0时会删除此行数据);如果是String等不可聚合的类型,则随机选择一个值。
通常建议将SummingMergeTree与MergeTree配合使用,使用MergeTree来存储明细数据,使用SummingMergeTree存储预聚合的数据来支撑加速查询。
-
创建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, ...]
SummingMergeTree参数说明
SummingMergeTree([columns])中的[columns]参数是表中的列,是可选的,该列是要汇总值的列名称的元组。这些列必须是数字类型,并且不能在主键中。如果不指定该列参数,ClickHouse会使用数值数据类型汇总所有非主键列的sum值。
-
创建SummingMergeTree引擎的tbl_test_summingmergetree表
create table tbl_test_summingmergetree( key UInt64, value UInt64 ) engine=SummingMergeTree() order by key;
-
第一次插入数据
insert into tbl_test_summingmergetree(key,value) values(1,13);
-
查询第一次插入的数据
select * from tbl_test_summingmergetree;
-
第二次插入重复数据
insert into tbl_test_summingmergetree(key,value) values(1,13);
-
查询表数据(有2条key=1的重复数据)
select * from tbl_test_summingmergetree;
-
第三次插入重复数据
insert into tbl_test_summingmergetree(key,value) values(1,16);
-
查询表数据(有3条key=1的重复数据)
select * from tbl_test_summingmergetree;
-
使用sum和count查询数据
sum函数用于计算value的和,count函数用于查看插入次数,group by用于保证是否合并完成都是准确的计算sum
select key,sum(value),count(value) from tbl_test_summingmergetree group by key;
-
手动触发重复数据的合并
optimize table tbl_test_summingmergetree final;
-
再次使用sum和count查询数据
select key,sum(value),count(value) from tbl_test_summingmergetree group by key;
结果集中key=1的count值变成1了,sum(value)的值是38。说明手动触发合并生效了。我们再来使用非聚合查询:
select * from tbl_test_summingmergetree;
此时,key=1的这条数据的确是合并完成了,由原来的3条变成1条了,而且value值的求和是正确的38。
3.4 AggregatingMergeTree
AggregatingMergeTree也是预聚合引擎的一种,是在MergeTree的基础上针对聚合函数计算结果进行增量计算用于提升聚合计算的性能。
与SummingMergeTree的区别在于:SummingMergeTree对非主键列进行sum聚合,而AggregatingMergeTree则可以指定各种聚合函数。
AggregatingMergeTree表适用于增量数据聚合,包括聚合的物化视图。
AggregatingMergeTree的语法比较复杂,需要结合物化视图或ClickHouse的特殊数据类型AggregateFunction一起使用。
在insert和select时,也有独特的写法和要求:写入时需要使用-State语法,查询时使用-Merge语法。
-
创建AggregatingMergeTree引擎表的的语法
语法结构
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2], ... ) ENGINE = AggregatingMergeTree() [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [TTL expr] [SETTINGS name=value, ...]
-
创建2张表
创建用户行为表
MergeTree引擎的用户行为表用来存储所有的用户行为数据,是后边AggregatingMergeTree引擎的UV和PV增量计算表的数据源。
因为AggregatingMergeTree的UV和PV增量计算表无法使用insert into tableName values语句插入,只能使用insert into tableName select语句才可以插入数据。
– 用户行为表
create table tbl_test_mergetree_logs( guid String, url String, refUrl String, cnt UInt16, cdt DateTime ) engine = MergeTree() partition by toYYYYMMDD(cdt) order by toYYYYMMDD(cdt);
插入数据到用户行为表
insert into tbl_test_mergetree_logs(guid,url,refUrl,cnt,cdt) values('a','www.baidu.com','image.baidu.com',1,'2019-12-17 12:12:12'),('a','www.baidu.com','image.baidu.com',1,'2019-12-17 12:14:45'),('b','www.baidu.com','image.baidu.com',1,'2019-12-17 13:13:13');
查询用户行为表所有数据
select * from tbl_test_mergetree_logs;
创建UV和PV增量计算表
– UV和PV增量计算表
create table tbl_test_aggregationmergetree_visitor( guid String, cnt AggregateFunction(count, UInt16), cdt Date ) engine = AggregatingMergeTree() partition by cdt order by cnt;
插入数据到UV和PV增量计算表
insert into tbl_test_aggregationmergetree_visitor select guid,countState(cnt),toDate(cdt) from tbl_test_mergetree_logs group by guid,cnt,cdt;
统计UV和PV增量计算表
select guid,count(cnt) from tbl_test_aggregationmergetree_visitor group by guid,cnt;
查询出的2条记录,guid列可以用来计算uv指标,count列可以用来计算pv值。
因为在插入数据的时候,根据guid执行group by计算,每个用户只有一条,所以统计guid列可得到uv指标;
使用countState(cnt)计算每个用户的所有访问次数,所以通过cnt列可以得到pv指标;
cdt字段作为分区字段,即增量聚合每天的uv和pv指标。
3.5 CollapsingMergeTree
在ClickHouse中不支持对数据update和delete操作(不能使用标准的更新和删除语法操作CK)
,但在增量计算场景下,状态更新是一个常见的现象,此时update操作似乎更符合这种需求。
ClickHouse提供了一个
CollapsingMergeTree
表引擎,
它继承于MergeTree引擎,是通过一种变通的方式来实现状态的更新。
CollapsingMergeTree表引擎需要的建表语句与MergeTree引擎基本一致,惟一的区别是需要指定Sign列(必须是Int8类型)。
这个Sign列有1和-1两个值,1表示为状态行,当需要新增一个状态时,需要将insert语句中的Sign列值设为1;-1表示为取消行,当需要删除一个状态时,需要将insert语句中的Sign列值设为-1。
这其实是插入了两行除Sign列值不同,但其他列值均相同的数据。因为有了Sign列的存在,当触发后台合并时,会找到存在状态行与取消行对应的数据,然后进行折叠操作,
也就是同时删除了这两行数据。
状态行与取消行不折叠有两种情况。
- 第一种是合并机制,由于合并在后台发生,且具体的执行时机不可预测,所以可能会存在状态行与取消行还没有被折叠的情况,这时会出现数据冗余;
- 第二种是当乱序插入时(CollapsingMergeTree仅允许严格连续插入),ClickHouse不能保证相同主键的行数据落在同一个节点上,但不同节点上的数据是无法折叠的。为了得到正确的查询结果,需要将count(col)、sum(col)改写成sum (Sign)、sum(col * Sign)。
如果在业务系统中使用ClickHouse的CollapsingMergeTree引擎表,当状态行已经存在,要插入取消行来删除数据的时候,必须存储一份状态行数据来执行insert语句删除。这种情况下,就有些麻烦,因为同一个业务数据的状态需要我们记录上一次原始态数据,和当前最新态的数据,才能完成原始态数据删除,最新态数据存储到ClickHouse中。
-
创建CollapsingMergeTree引擎表的语法
语法结构
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2], ... ) ENGINE = CollapsingMergeTree(sign) [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
CollapsingMergeTree(sign)参数说明
Sign是列名称,必须是Int8类型,用来标志Sign列。Sign列值为1是状态行,为-1是取消行。
-
创建CollapsingMergeTree引擎的tbl_test_collapsingmergetree_day_mall_sale_all表
create table tbl_test_collapsingmergetree_day_mall_sale ( mallId UInt64, mallName String, totalAmount Decimal(32,2), cdt Date, sign Int8 ) engine=CollapsingMergeTree(sign) partition by toYYYYMMDD(cdt) order by mallId;
-
第一次插入2条sign=1的数据
注意:当一行数据的sign列=1时,是标记该行数据属于状态行。也就是说,我们插入了两条状态行数据
insert into tbl_test_collapsingmergetree_day_mall_sale(mallId,mallName,totalAmount,cdt,sign) values(1,'西单大悦城',17649135.64,'2019-12-24',1); insert into tbl_test_collapsingmergetree_day_mall_sale(mallId,mallName,totalAmount,cdt,sign) values(2,'朝阳大悦城',16341742.99,'2019-12-24',1);
-
查询第一次插入的数据
select * from tbl_test_collapsingmergetree_day_mall_sale;
-
第二次插入2条sign=-1的数据
注意:当一行数据的sign列=-1时,是标记该行数据属于取消行(取消行有一个要求:除了sign字段值不同,其他字段值必须是相同的。这样一来,就有点麻烦,因为我们在状态发生变化时,还需要保存着未发生状态变化的数据。这个场景类似于修改数据,但由于ClickHouse本身的特性不支持update,所以其提供了一种变通的方式,即通过CollapsingMergeTree引擎来支持这个场景)。取消行指的是当这一行数据有了新的状态变化,需要先取消原来存储的数据,使ClickHouse合并时来删除这些sign由1变成-1的数据,虽然合并发生时机不确定,但如果触发了合并操作就一定会被删除。这样一来,我们将有新状态变化的数据再次插入到表,就仍然是2条数据。
insert into tbl_test_collapsingmergetree_day_mall_sale(mallId,mallName,totalAmount,cdt,sign) values(1,'西单大悦城',17649135.64,'2019-12-24',-1); insert into tbl_test_collapsingmergetree_day_mall_sale(mallId,mallName,totalAmount,cdt,sign) values(2,'朝阳大悦城',16341742.99,'2019-12-24',-1);
-
对表执行强制合并
optimize table tbl_test_collapsingmergetree_day_mall_sale final;
然后发现查询数据时,表中已经没有了数据。这表示当触发合并操作时,会合并状态行与取消行同时存在的数据。
3.6 VersionedCollapsingMergeTree
该引擎继承自 MergeTree 并将折叠行的逻辑添加到合并数据部分的算法中,这个引擎:
- 允许快速写入不断变化的对象状态
- 删除后台中的旧对象状态,这显著降低了存储体积
VersionedCollapsingMergeTree 用于相同的目的 折叠树,但使用不同的折叠算法,允许以多个线程的任何顺序插入数据。 特别是, Version 列有助于正确折叠行,即使它们以错误的顺序插入。
相比之下, CollapsingMergeTree 只允许严格连续插入。
-
创建VersionedCollapsingMergeTree引擎表的语法
语法结构
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2], ... ) ENGINE = VersionedCollapsingMergeTree(sign, version) [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
VersionedCollapsingMergeTree(sign)参数说明
Sign是列名称,必须是Int8类型,用来标志Sign列。Sign列值为1是状态行,为-1是取消行。
-
折叠数据
考虑一种情况,您需要为某个对象保存不断变化的数据。对于一个对象有一行,并在发生更改时更新该行是合理的。但是,对于数据库管理系统来说,更新操作非常昂贵且速度很慢,因为它需要重写存储中的数据。 如果需要快速写入数据,则不能接受更新,但可以按如下顺序将更改写入对象。
使用 Sign 列写入行时。 如果 Sign = 1 这意味着该行是一个对象的状态(让我们把它称为 “state” 行)。 如果 Sign = -1 它指示具有相同属性的对象的状态的取消(让我们称之为 “cancel” 行)。 还可以使用 Version 列,它应该用单独的数字标识对象的每个状态。
例如,我们要计算用户在某个网站上访问了多少页面以及他们在那里的时间。
在某个时间点,我们用用户活动的状态写下面的行:
┌───────────UserID───┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐ │ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 | └───────────────────┴─────────┴───────┴─────┴────────┘
在稍后的某个时候,我们注册用户活动的变化,并用以下两行写入它。
┌────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐ │ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 | │ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 | └─────────────────┴──────────┴────────┴────┴──────┘
第一行取消对象(用户)的先前状态。 它应该复制已取消状态的所有字段,除了 Sign.
第二行包含当前状态。因为我们只需要用户活动的最后一个状态行
┌────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐ │ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 | │ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 | └─────────────────┴──────────┴────────┴─────┴──────┘
可以删除,折叠对象的无效(旧)状态。 VersionedCollapsingMergeTree 在合并数据部分时执行此操作。
-
使用示例
┌───────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐ │ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 | │ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 | │ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 | └─────────────────┴────────┴────────┴──────┴─────────┘
创建表:
CREATE TABLE UAct ( UserID UInt64, PageViews UInt8, Duration UInt8, Sign Int8, Version UInt8 ) ENGINE = VersionedCollapsingMergeTree(Sign, Version) ORDER BY UserID
插入数据:
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1, 1); INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1, 1),(4324182021466249494, 6, 185, 1, 2);
我们用两个 INSERT 插入以创建两个不同的数据部分。 如果我们使用单个查询插入数据,ClickHouse将创建一个数据部分,并且永远不会执行任何合并。
获取数据:
SELECT * FROM UAct ┌────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐ │ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 │ └─────────────────┴────────┴────────┴────┴──────┘ ┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐ │ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 │ │ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │ └─────────────────┴────────┴───────┴─────┴───────┘
我们在这里看到了什么,折叠的部分在哪里?
我们使用两个数据部分的 INSERT 查询。该 SELECT 查询是在两个线程中执行的,结果是行的随机顺序。
由于数据部分尚未合并,因此未发生折叠。 ClickHouse在我们无法预测的未知时间点合并数据部分。这就是为什么我们需要聚合:
SELECT UserID, sum(PageViews * Sign) AS PageViews, sum(Duration * Sign) AS Duration, Version FROM UAct GROUP BY UserID, Version HAVING sum(Sign) > 0; ┌────────────UserID─┬─PageViews─┬─Duration┬─Version─┐ │ 4324182021466249494 │ 6 │ 185 │ 2 │ └─────────────────┴──────────┴───────┴───────┘
如果我们不需要聚合,并希望强制折叠,我们可以使用
FINAL
修饰符
FROM
SELECT * FROM UAct FINAL ┌────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐ │ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │ └─────────────────┴──────────┴───────┴────┴─────────┘
这是一个非常低效的方式来选择数据。 不要把它用于数据量大的表。