表引擎在ClickHouse中扮演重要角色,直接决定如何存储、读取数据,是否支持并法读写,是否支持索引、查询类型、主从复制等。
ClickHouse提供4类表引擎,分别支持不同场景。如Log系列用于小型表数据分析,MergeTree系列实现大型表数据分析,集成系列用于数据集成。考虑到这么多类型对于新用户经来说难以理解、也难以选择,本文试图对各类引擎进行整理,加深理解,希望对你也有帮助。另外复制系列和分布式表引擎比较复杂,未来再学习分享。
下图展示了官方文档中列举至此的所有表引擎:
图1
log系列表引擎功能相对简单。主要用于快速写小规模数据(少于100万行),然后全部读出来。Log表引擎有几个通用特征:
它们之间的差异为:
该系列引擎主要用于导入外部数据至ClickHouse或在ClickHouse中直接操作外部数据源:
上述几种引擎主要用于特定目的,使用场景有一定限制。MegerTree系列引擎是官方提供主要存储引擎,支持所有ClickHouse所有核心功能。
下面聚焦MegerTree、replaceingmergetree, CollapsingMergeTree, VersionedCollapsingMergeTree, SummingMergeTree, 以及 AggregatingMergeTree 引擎。
MegerTree表引擎主要用于海量数据分析、支持数据分区,顺序存储、主键索引、稀疏索引、数据TTL等。egerTree至此所有ClickHouse SQL语法,但一些功能与MySQL有差异。举例,主键不保证数据重复性。
下面示例创建MegerTree表test_tbl,主键为id, create_time,数据会按照主键进行排序存储,数据根据create_time进行分区,数据仅保存至上个月。
CREATE TABLE test_tbl (
id UInt16,
create_time Date,
comment Nullable(String)
) ENGINE = MergeTree()
PARTITION BY create_time
ORDER BY (id, create_time)
PRIMARY KEY (id, create_time)
TTL create_time + INTERVAL 1 MONTH
SETTINGS index_granularity=8192;
下面写一些示例数据,但我们示例数据有主键重复的数据:
insert into test_tbl values(0, '2023-03-12', null);
insert into test_tbl values(0, '2023-03-12', null);
insert into test_tbl values(1, '2023-03-13', null);
insert into test_tbl values(1, '2023-03-13', null);
insert into test_tbl values(2, '2023-03-14', null);
查询数据:虽然相同主键的数据仅有3条,但查询结果为5条。
SELECT count(*) FROM test_tbl
┌─count()─┐
│ 5 │
└─────────┘
SELECT * FROM test_tbl
┌─id─┬─create_time─┬─comment─┐
│ 2 │ 2023-03-14 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 1 │ 2023-03-13 │ ᴺᵁᴸᴸ │
│ 1 │ 2023-03-13 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 0 │ 2023-03-12 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 0 │ 2023-03-12 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
因为MergeTree使用了一种类似于LSM树的结构,很多存储层处理逻辑直到比较期间才会发生。因此,在强制执行后台压缩后,再次查询,仍然有5条数据,但因为我们定义了分区,数据被重新组织。
optimize table test_tbl final;
SELECT * FROM test_tbl
┌─id─┬─create_time─┬─comment─┐
│ 2 │ 2023-03-14 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 1 │ 2023-03-13 │ ᴺᵁᴸᴸ │
│ 1 │ 2023-03-13 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 0 │ 2023-03-12 │ ᴺᵁᴸᴸ │
│ 0 │ 2023-03-12 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
上面示例看到,MegerTree虽然有主键,主要用于家属查询,而不像MySQL保证记录唯一性。即使完成比较后,相同主键的数据行仍在一起。
为了解决MegerTree相同主键问题,ClickHouse提供了ReplacingMergeTree引擎,实现记录去重,请看示例:
-- Table building
CREATE TABLE test_tbl_replacing (
id UInt16,
create_time Date,
comment Nullable(String)
) ENGINE = ReplacingMergeTree()
PARTITION BY create_time
ORDER BY (id, create_time)
PRIMARY KEY (id, create_time)
TTL create_time + INTERVAL 1 MONTH
SETTINGS index_granularity=8192;
-- Write duplicate primary key data
insert into test_tbl_replacing values(0, '2023-03--12', null);
insert into test_tbl_replacing values(0, '2023-03--12', null);
insert into test_tbl_replacing values(1, '2023-03--13', null);
insert into test_tbl_replacing values(1, '2023-03--13', null);
insert into test_tbl_replacing values(2, '2023-03--14', null);
SELECT *
FROM test_tbl_replacing
Query id: 8b96a5d3-5089-4721-9b88-9ec86ae4816a
┌─id─┬─create_time─┬─comment─┐
│ 2 │ 2023-03-14 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 0 │ 2023-03-12 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 1 │ 2023-03-13 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 0 │ 2023-03-12 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
-- Force background compaction:
optimize table test_tbl_replacing final;
SELECT *
FROM test_tbl_replacing
Query id: 33efdfe6-c8f1-4428-8307-352ee4c1d71b
┌─id─┬─create_time─┬─comment─┐
│ 2 │ 2023-03-14 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 1 │ 2023-03-13 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
┌─id─┬─create_time─┬─comment─┐
│ 0 │ 2023-03-12 │ ᴺᵁᴸᴸ │
└────┴─────────────┴─────────┘
虽然replaceingmergetree提供去重功能,但仍有一定限制:
因此,replaceingmergetree引擎在数据最终保证去重场景中更有用,在查询过程中,不能保证主键数据去重。
ClickHouse实现CollapsingMergeTree(折叠合并树)引擎为了消除ReplacingMergeTree的限制。该引擎需要一个标签列:Sign, 在创建表时指定。在后端比较时,相同主键行和相反Sign将被折叠,就是被删除。
CollapsingMergeTree数据行根据Sign直分为了两类:Sign=1 称为状态行,Sign=-1称为取消行。每次需要写数据时需要新的状态行,反之删除数据时需要取消行。
在后端比较时,状态行和取消行被自动折叠(删除)。如果还未开始比较,状态行和取消行同时存在。因此,为了实现相同主键记录被折叠,业务曾需要相应逻辑进行支持:
-- Table building
CREATE TABLE UAct
(
UserID UInt64,
PageViews UInt8,
Duration UInt8,
Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID;
-- Insert status line, note sign The value of a column is 1
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1);
-- Insert a cancel line to offset the above status line. be careful sign The value of a column is-1,Other values are consistent with the status line;
-- And insert a new status row with the same primary key, which is used to PageViews Update from 5 to 6, will Duration Update from 146 to 185.
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1), (4324182021466249494, 6, 185, 1);
SELECT * FROM UAct
Query id: c4ca984b-ac9d-46df-bdba-b2cf4e98dc1f
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
-- 为了获得正确sum结果,我们使用下面SQL:
SELECT
UserID,
sum(PageViews * Sign) AS PageViews,
sum(Duration * Sign) AS Duration
FROM UAct
GROUP BY UserID
HAVING sum(Sign) > 0
Query id: 829e4c7f-11af-47fc-b8d9-3f3a3105d491
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │ 6 │ 185 │
└─────────────────────┴───────────┴──────────┘
-- Force background Compaction
optimize table UAct final;
SELECT * FROM UAct
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
虽然CollapsingMergeTree解决了用同一个主键瞬间删除数据的问题,但在连续状态改变和多线程并行写入的情况下,状态行和取消行位置可能乱序,导致无法正常折叠。
下面示例为乱序记录导致不能折叠示例:
-- Table building
CREATE TABLE UAct_order
(
UserID UInt64,
PageViews UInt8,
Duration UInt8,
Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID;
-- Insert cancel line first
INSERT INTO UAct_order VALUES (4324182021466249495, 5, 146, -1);
-- Insert status line after
INSERT INTO UAct_order VALUES (4324182021466249495, 5, 146, 1);
-- force Compaction
optimize table UAct_order final;
-- You can see that even if Compaction After that, the primary key cannot be folded: 2 Row data still exists.
select * from UAct_order;
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249495 │ 5 │ 146 │ -1 │
│ 4324182021466249495 │ 5 │ 146 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
为了解决CollapsingMergeTree引擎乱序不能折叠问题,VersionedCollapsingMergeTree引擎在创建表时增加版本列,用于记录乱序写入时状态行和取消行之间的对应关系。在比较时相同主键、相同版本、相反Sign的记录被删除。
与CollapsingMergeTree类似,为了获得正确结果,业务曾仍需要重写SQL,分别修改 count(), sum(col) 为 sum(Sign), sum(col * Sign)。请看示例:
-- Table building
CREATE TABLE UAct_version
(
UserID UInt64,
PageViews UInt8,
Duration UInt8,
Sign Int8,
Version UInt8
)
ENGINE = VersionedCollapsingMergeTree(Sign, Version)
ORDER BY UserID;
-- Insert a row first to cancel the row, note Signz=-1, Version=1
INSERT INTO UAct_version VALUES (4324182021466249494, 5, 146, -1, 1);
-- Insert a status line after, note Sign=1, Version=1;
-- And a new status line note Sign=1, Version=2,take PageViews Update from 5 to 6, will Duration Update from 146 to 185.
INSERT INTO UAct_version VALUES (4324182021466249494, 5, 146, 1, 1),(4324182021466249494, 6, 185, 1, 2);
-- 为比较之前查询,显示所有行记录.
SELECT * FROM UAct_version;
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
-- 为了获取正确结果,重写下面SQL:
-- sum(PageViews) => sum(PageViews * Sign),
-- sum(Duration) => sum(Duration * Sign)
SELECT
UserID,
sum(PageViews * Sign) AS PageViews,
sum(Duration * Sign) AS Duration
FROM UAct_version
GROUP BY UserID
HAVING sum(Sign) > 0;
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │ 6 │ 185 │
└─────────────────────┴───────────┴──────────┘
-- 强制后端比较
optimize table UAct_version final;
-- 查询后,即使顺序乱,但仍获得正确结果.
select * from UAct_version;
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
ClickHouse的SummingMergeTree引擎提供按照主键列进行聚合求和。在后端比较时,汇总相同主键的多个行,然后使用一行代替,这样既缩减了存储空间,也提升聚集计算的性能。需要注意下面三点:
请看示例:
-- 创建表Table
CREATE TABLE summtt
(
key UInt32,
value UInt32
)
ENGINE = SummingMergeTree()
ORDER BY key
-- 插入数据
INSERT INTO summtt Values(1,1),(1,2),(2,1)
-- 比较前,相同主键记录同时存在
select * from summtt;
┌─key─┬─value─┐
│ 1 │ 1 │
│ 1 │ 2 │
│ 2 │ 1 │
└─────┴───────┘
-- 采用 GROUP BY 执行聚合计算
SELECT key, sum(value) FROM summtt GROUP BY key
┌─key─┬─sum(value)─┐
│ 2 │ 1 │
│ 1 │ 3 │
└─────┴────────────┘
-- 强制比较
optimize table summtt final;
-- 比较后查询,可以看到相同主键记录已经聚合
select * from summtt;
┌─key─┬─value─┐
│ 1 │ 3 │
│ 2 │ 1 │
└─────┴───────┘
-- 实际使用时,仍然需要分组查询
SELECT key, sum(value) FROM summtt GROUP BY key
┌─key─┬─sum(value)─┐
│ 2 │ 1 │
│ 1 │ 3 │
└─────┴────────────┘
AggregatingMergeTree也是一种预聚合引擎,用于提升聚合计算性能。与SummingMergeTree的差异是,SummingMergeTree聚合非主键列,而AggregatingMergeTree可以指定不同的聚合函数。
AggregatingMergeTree语法稍微有点复杂,需要和物化视图或特定类型的聚合函数一起使用。在insert,select中,插入时需要使用State语法,查询需要使用Merge语法。
请看示例1,使用复杂视图:
-- Schedule
CREATE TABLE visits
(
UserID UInt64,
CounterID UInt8,
StartDate Date,
Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID;
-- Create a materialized view of the schedule, which pre aggregates the schedule
-- Note: the functions used for pre aggregation are: sumState, uniqState. Corresponding to write syntax-State.
CREATE MATERIALIZED VIEW visits_agg_view
ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(StartDate) ORDER BY (CounterID, StartDate)
AS SELECT
CounterID,
StartDate,
sumState(Sign) AS Visits,
uniqState(UserID) AS Users
FROM visits
GROUP BY CounterID, StartDate;
-- Insert detail data
INSERT INTO visits VALUES(0, 0, '2019-11-11', 1);
INSERT INTO visits VALUES(1, 1, '2019-11-12', 1);
-- Final aggregation of materialized views
-- Note: the aggregate function used is sumMerge, uniqMerge. Corresponding to query syntax-Merge.
SELECT
StartDate,
sumMerge(Visits) AS Visits,
uniqMerge(Users) AS Users
FROM visits_agg_view
GROUP BY StartDate
ORDER BY StartDate;
-- Ordinary function sum, uniq No longer available
-- as follows SQL Error will be reported: Illegal type AggregateFunction(sum, Int8) of argument
SELECT
StartDate,
sum(Visits),
uniq(Users)
FROM visits_agg_view
GROUP BY StartDate
ORDER BY StartDate;
示例2:
-- Schedule
CREATE TABLE detail_table
( CounterID UInt8,
StartDate Date,
UserID UInt64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate);
-- Insert detail data
INSERT INTO detail_table VALUES(0, '2019-11-11', 1);
INSERT INTO detail_table VALUES(1, '2019-11-12', 1);
-- Create a prepolymerization table,
-- Note: among them UserID The type of a column is: AggregateFunction(uniq, UInt64)
CREATE TABLE agg_table
( CounterID UInt8,
StartDate Date,
UserID AggregateFunction(uniq, UInt64)
) ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate);
-- Read data from the parts list and insert the aggregate table.
-- Note: the aggregate function used in the subquery is uniqState, Corresponding to write syntax-State
INSERT INTO agg_table
select CounterID, StartDate, uniqState(UserID)
from detail_table
group by CounterID, StartDate
-- Cannot use normal insert Statement direction AggregatingMergeTree Insert data in.
-- book SQL Error will be reported: Cannot convert UInt64 to AggregateFunction(uniq, UInt64)
INSERT INTO agg_table VALUES(1, '2019-11-12', 1);
-- Query from aggregate table.
-- Be careful: select The aggregate function used in is uniqMerge,Corresponding to query syntax-Merge
SELECT uniqMerge(UserID) AS state
FROM agg_table
GROUP BY CounterID, StartDate;
本文总体介绍了ClickHouse表引擎,重点按流程详细介绍了MegerTree系列表引擎,并通过实例进行验证,希望对你有帮助。参考资料:https://programmer.help/blogs/how-to-choose-clickhouse-table-engine.html;官方文档:https://clickhouse.com/docs/en/engines/table-engines#mergetree