ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ClickHouse介绍(三)MergeTree系列表引擎

2021-04-16 23:03:57  阅读:261  来源: 互联网

标签:00 17 id 引擎 2019 table 数据 MergeTree ClickHouse


MergeTree系列表引擎

ClickHouse中最核心的引擎当属MergeTree系列引擎,其中基础表引擎为MergeTree,常用的表引擎还有ReplacingMergeTree、SummingMergeTree、AggregatingMergeTree、CollapsingMergeTree和VersionedCollapsingMergeTree。每一种MergeTree的变种,在继承了基础MergeTree的能力后,还增加了它独有的特性。其名称中的“合并”二字奠定了所有类型MergeTree的基因,它们的所有特殊逻辑,都是在触发合并的过程中被激活的。接下来主要介绍它们各自的特点与使用方法。

 

1. ReplacingMergeTree

在MergeTree中,虽然有主键,但是它没有唯一键的约束。也就是说,写入数据的主键是可以重复的。但在某些场合下,用户不希望表中有重复数据,ReplacingMergeTree就是为此场景而设计,它可以在合并分区时,删除重复的数据条目。不过此方法仅是在“一定程度”上解决了重复数据的问题,稍后会对此作出解释。

创建测试表:

CREATE TABLE replace_table
(
    `id` String,
    `code` String,
    `create_time` DateTime
)
ENGINE = ReplacingMergeTree
PARTITION BY toYYYYMM(create_time)
PRIMARY KEY id
ORDER BY (id, code)

这里ORDER BY 是去除重复数据的关键,排序键ORDER BY所声明的表达式是后续判断是否有重复数据的依据。在这个例子中,数据会基于id 和 code 两个字段做去重。

分2个批次插入数据:

INSERT INTO replace_table VALUES 
('A001', 'C1', '2019-05-10 17:00:00'),
('A001', 'C2', '2019-05-14 17:00:00'),
('A001', 'C3', '2019-05-15 17:00:00')  

INSERT INTO replace_table VALUES 
('A001', 'C1', '2019-05-11 17:00:00'),
('A001', 'C100', '2019-05-12 17:00:00'),
('A001', 'C200', '2019-05-13 17:00:00')

# 查询数据:
SELECT *
FROM replace_table

┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
└──────┴──────┴─────────────────────┘
┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-10 17:00:00 │
│ A001 │ C2 │ 2019-05-14 17:00:00 │
│ A001 │ C3 │ 2019-05-15 17:00:00 │
└──────┴──────┴─────────────────────┘

 

 

可以看到在表中是存在重复的id与<id, code>,使用 OPTIMIZE 命令强制触发合并后再次查看数据条目:

SELECT *
FROM replace_table


┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C2 │ 2019-05-14 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
│ A001 │ C3 │ 2019-05-15 17:00:00 │
└──────┴──────┴─────────────────────┘

 

可以看到<id, code> 重复的条目已经被删除了。在optimize强制触发合并后,会按照<id, code> 进行分组,并保留分组内最后一条条目(观察create_time日期字段)。

从测试结果可以知道,ReplacingMergeTree在去除重复数据时,是以ORDER BY排序键作为基准,而不是PRIMARY KEY。

前面提到ReplacingMergeTree仅在“一定程度上”解决了数据重复的问题,现在我们解释这点。首先,再插入一条数据:

INSERT INTO replace_table VALUES ('A001', 'C1', '2019-08-10 17:00:00')

 

执行 OPTIMIZE TABLE 后再查看数据:

OPTIMIZE TABLE replace_table FINAL

SELECT *
FROM replace_table

┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-08-10 17:00:00 │
└──────┴──────┴─────────────────────┘
┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C2 │ 2019-05-14 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
│ A001 │ C3 │ 2019-05-15 17:00:00 │
└──────┴──────┴─────────────────────┘

可以看到表中存在相同的<id, code> 行,但是并没有执行去重。这是因为ReplacingMergeTree是以分区为单位进行合并,并在此时删除重复数据。不同分区之间的重复数据无法进行删除。所以ReplacingMergeTree仅是在“一定程度上“解决了数据重复问题。

 

1.2. 版本号

ReplacingMergeTree中可以根据版本号来决定:在对重复数据进行去重时,保留哪条数据。

例如以下建表语句:

CREATE TABLE replace_table
(
    `id` String,
    `code` String,
    `create_time` DateTime
)
ENGINE = ReplacingMergeTree(create_time)
PARTITION BY toYYYYMM(create_time)
ORDER BY id

可以看到在指定ReplacingMergeTree引擎时,传入了一个列字段create_time。这个表基于id字段进行去重,并且使用create_time作为版本号。

在使用create_time作为版本号后,在对数据进行去重时,会保留id重复的条目中,create_time时间最长的一行。

 

1.3. ReplacingMergeTree总结

ReplacingMergeTree的处理逻辑为:

  1. 使用ORDER BY 排序键作为判断数据是否重复的唯一键
  2. 只有在合并分区时才会触发删除重复数据的逻辑
  3. 仅有相同分区的数据会进行去重,无法跨分区进行去重
  4. 在进行去重操作时,由于分区内的数据已经基于ORDER BY进行了排序,所以能够找到那些相邻的重复数据
  5. 数据去重策略有2种:
    1. 如果没有设置ver版本号,则保留同一组重复数据的最后一行
    2. 如果设置了ver版本号,则保留同一组重复数据中ver字段取值最大的那一行

 

2. SummingMergeTree

SummingMergeTree用于仅关系聚合结果,而不关心具体明细的场景。并且数据汇总条件是预先明确的(group by 的条件明确,不会随意更改)。

SummingMergeTree可以在合并分区时,按照预先定义的条件,聚合汇总数据,将同一分区下的多行数据聚合成一行。这样既减少了数据行,又降低了后续聚合查询的开销。

 

2.1. ORDER BY 与 PRIMARY KEY

在MergeTree中,数据会按照ORDER BY 的字段在分区内进行排序。主键索引也会按照PRIMARY KEY 表达式取值并排序。而ORDER BY可以指代主键,所以在一般情况下,只单独声明ORDER BY即可,此时数据排序与主键索引均相同。

如果需要同时定义ORDER BY与PRIMARY KEY,便是明确希望他俩不同。这种情况通常会在使用SummingMergeTree或AggregatingMergeTree时会出现,因为它们的聚合都是根据ORDER BY 进行的。由此可以引出2点原因:主键与聚合的条件定义分离,为修改聚合条件留下空间。

举个例子,假设一张表使用的引擎为SummingMergeTree,包含6个字段,分别为A、B、C、D、E、F。如果需要按照A、B、C、D进行汇总,则有:

ORDER BY (A, B, C, D)

 

但是这样的写法也表示:表的PRIMARY KEY 被定义成A, B, C, D。若是在业务层面,仅需要对字段A进行查询过滤,则应只使用A字段创建主键。所以更好的一种写法是:

ORDER BY (A, B, C, D)

PRIMARY KEY A

 

如果同时声明了ORDER BY 与 PRIMARY KEY,则MergeTree会强制要求PRIMARY KEY列字段必须是ORDER BY的前缀。

例如下面的定义是错的:

ORDER BY (B, C)

PRIMARY KEY A

 

下面的是正确的:

ORDER BY (B, C)

PRIMARY KEY B

 

这种强制约束保障了即便在两者定义不同的情况下,主键认识排序键的前提,不会出现索引与数据顺序混乱的问题。

假设现在业务发生了细微的变化,需要减少字段,将先前的A、B、C、D改为按照A、B进行聚合,则可以按以下方式修改排序键:

ALTER TABLE table_name MODIFY ORDER BY (A, B)

 

在修改ORDER BY 时会有一些限制,只能在现有的基础上减少字段。如果是新增排序字段,则只能添加通过ALTER ADD COLUMN 新增的字段。但是ALTER 是一种元数据的操作,修改成本很低,相比不能被修改的主键,这已经非常便利了。

 

2.2. SummingMergeTree的使用

SummingMergeTree引擎的声明方式为:

ENGINE = SummingMergeTree((col1, col2, …))

 

这里col1,col2是选填参数,用于设置除主键外的其他数值类型字段,以指定被SUM聚合的列字段。若不指定此字段,则所有非主键的数值型字段均会进行SUM聚合。

下面举例说明:

创建表:

CREATE TABLE summing_table
(
    `id` String,
    `city` String,
    `v1` UInt32,
    `v2` Float64,
    `create_time` DateTime
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(create_time)
PRIMARY KEY id
ORDER BY (id, city)

 

这里 order by 是关键配置,数据会以 order by 指定的列为维度进行聚合。

分批次插入数据:

INSERT INTO summing_table VALUES
('A001', 'wuhan', '10', '20', '2019-08-10 17:00:00')
 ('A001', 'jinzhou', '20', '30', '2019-08-10 17:00:00')

INSERT INTO summing_table VALUES
('A001', 'wuhan', '10', '20', '2019-02-10 17:00:00')
('A001', 'wuhan', '20', '30', '2019-08-20 17:00:00')

INSERT INTO summing_table VALUES
('A002', 'wuhan', '60', '50', '2019-10-10 17:00:00')

 

当前数据结果:

SELECT *
FROM summing_table


┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ wuhan │ 20 │ 30 │ 2019-08-20 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘
┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ wuhan │ 10 │ 20 │ 2019-02-10 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘
┌─id───┬─city────┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ jinzhou │ 20 │ 30 │ 2019-08-10 17:00:00 │
│ A001 │ wuhan   │ 10 │ 20 │ 2019-08-10 17:00:00 │
└──────┴─────────┴────┴────┴─────────────────────┘
┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A002 │ wuhan │ 60 │ 50 │ 2019-10-10 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘

 

执行 OPTIMIZE TABLE 后查看结果:

OPTIMIZE TABLE summing_table FINAL

SELECT *
FROM summing_table


┌─id───┬─city────┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ jinzhou │ 20 │ 30 │ 2019-08-10 17:00:00 │
│ A001 │ wuhan   │ 30 │ 50 │ 2019-08-10 17:00:00 │
└──────┴─────────┴────┴────┴─────────────────────┘
┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ wuhan │ 10 │ 20 │ 2019-02-10 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘
┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A002 │ wuhan │ 60 │ 50 │ 2019-10-10 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘

可以看到,在不同分区内,数据以ORDER BY 的字段<id, city> 进行了聚合,聚合的字段为v1 与 v2。而非可聚合字段create_time 取的是同组内第一行数据的取值。

 

SummingMergeTree也支持嵌套类型的字段,在使用嵌套类型字段时,需要被SUM聚合的字段名称必须以Map后缀结尾,例如:

CREATE TABLE summing_table_nested
(
    `id` String,
    `nestMap` Nested(id UInt32, key UInt32, val UInt64),
    `create_time` DateTime
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY id

 

插入一条数据并查看结果:

INSERT INTO summing_table_nested VALUES
('A001', [1,1,2], [10,20,30], [40,50,60], '2019-08-10 17:00:00')

SELECT *
FROM summing_table_nested


┌─id───┬─nestMap.id─┬─nestMap.key─┬─nestMap.val─┬─────────create_time─┐
│ A001 │ [1,2]      │ [30,30]     │ [90,60]     │ 2019-08-10 17:00:00 │
└──────┴────────────┴─────────────┴─────────────┴─────────────────────┘

可以看到此结果是按照 id 进行了聚合,相同 nestMap.id 的条目在nestMap.key 与 nestMap.val 上进行了sum 聚合。

 

在使用嵌套数据类型的时候,也支持使用复合Key作为数据聚合的条件。为了使用复合Key,在嵌套类型的字段中,除第一个字段外,任何名称是以Key、Id或Type为后缀结尾的字段,都将和第一个字段一起组合成为复合Key。例如将上面的例子中小写key改为大写Key:

(

    `id` String,

    `nestMap` Nested(id UInt32, Key UInt32, val UInt64),

    `create_time` DateTime

)

 

这样数据就会以<id, Key> 作为条件进行聚合。

 

2.3. SummingMergeTree总结

SummingMergeTree的处理逻辑为:

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

 

3. AggregatingMergeTree

相信大家应该有了解过“数据立方体“(Cube)的概念,它在数仓领域非常常见。它的主要理念是:通过空间换取时间,对需要聚合的数据进行预计算,并将结果保存。在后续进行聚合查询的时候,可以直接使用结果数据,提升查询性能。

AggregatingMergeTree有些许“数据立方体“的意思,它能在合并分区的时候,按照预先定义的条件聚合数据。同时,根据预先定义的聚合函数,计算数据并以二进制的格式存入表内。

将同一分组下的多行数据聚合成一行,既减少了数据行,又降低了后续聚合查询的开销。可以说AggregatingMergeTree是SummingMergeTree的升级版,它们的许多设计思路是一致的,例如同时定义ORDER BY与PRIMARY KEY的原因和目的。但在使用方法上,两者存在明显差异。

AggregatingMergeTree没有任何额外参数,在分区合并时,在每个数据分区内,会按照ORDER BY聚合。而使用何种聚合函数,以及针对哪些列字段计算,则是通过定义AggregateFunction数据类型实现的。

 

3.1. AggregatingMergeTree的使用

如下建表语句:

CREATE TABLE agg_table
(
    `id` String,
    `city` String,
    `code` AggregateFunction(uniq, String),
    `value` AggregateFunction(sum, UInt32),
    `create_time` DateTime
)
ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(create_time)
PRIMARY KEY id
ORDER BY (id, city)

上例中列字段id和city是聚合条件,等同于下面语义:

GROUP BY id, city

 

而 code 和 value 是聚合字段,其语义等同于:

UNIQ(code), SUM(value)

 

AggregateFunction是ClickHouse提供的一种特殊的数据类型,它能够以二进制的形式存储中间状态结果。其使用方法也非常特殊,对于AggregateFunction类型的列字段,数据的写入和查询都与寻常不同:

  1. 在写入数据时,需要调用 *State函数;
  2. 在查询数据时,需要调用相应的*Merge函数

这里*表示定义时使用的聚合函数。

例如,在示例中定义的code和value使用了uniq和sum函数:

code AggregateFunction(uniq, String)

value AggregateFunction(sum, UInt32)

 

则在写入数据时需要调用与uniq、sum对应的uniqState和sumState函数,并使用INSERT SELECT语法:

INSERT INTO TABLE agg_table
SELECT 'A000','wuhan',
uniqState('code1'),
sumState(toUInt32(100)),
'2019-09-10 17:00:00'

 

在查询数据时,如果直接使用列名去访问这里的code 与 value列时,结果为乱码。这是因为它们的格式为二进制格式,在查询时需要调用与uniq、sum对应的uniqMerge和sumMerge函数:

SELECT
    id,
    city,
    uniqMerge(code),
    sumMerge(value)
FROM agg_table
GROUP BY
    id,
    city

┌─id───┬─city──┬─uniqMerge(code)─┬─sumMerge(value)─┐
│ A000 │ wuhan │       1         │       100       │
└──────┴───────┴─────────────────┴─────────────────┘

 

以上 insert into 的方式并非是AggregatingMergeTree的常见用法,它更为常见的用法是结合物化视图使用,将它作为物化视图表的引擎。

 

3.2. 结合物化视图使用

举例说明,首先建立明细数据表(也就是俗称的底表):

CREATE TABLE agg_table_basic
(
    `id` String,
    `city` String,
    `code` String,
    `value` UInt32
)
ENGINE = MergeTree()
PARTITION BY city
ORDER BY (id, city)

 

通常使用MergeTree作为底表,用于存储全量的明细数据,并以此对外提供实时查询。

然后建立一张物化视图:

CREATE MATERIALIZED VIEW agg_view
ENGINE = AggregatingMergeTree()
PARTITION BY city
ORDER BY (id, city) AS
SELECT
    id,
    city,
    uniqState(code) AS code,
    sumState(value) AS value
FROM agg_table_basic
GROUP BY
    id,
city

 

物化视图使用AggregatingMergeTree表引擎,用于特定场景的数据查询,相比MergeTree,它拥有更高的性能。

在新增数据时,面向的对象是底表MergeTree,例如:

INSERT INTO TABLE agg_table_basic VALUES
('A000','wuhan','code1',100),('A000','wuhan','code2',200),('A000','zhuhai','code1',200)
    
SELECT *
FROM agg_table_basic

┌─id───┬─city───┬─code──┬─value─┐
│ A000 │ zhuhai │ code1 │  200  │
└──────┴────────┴───────┴───────┘
┌─id───┬─city──┬─code──┬─value─┐
│ A000 │ wuhan │ code1 │  100  │
│ A000 │ wuhan │ code2 │  200  │
└──────┴───────┴───────┴───────┘

 

数据会自动同步到物化视图,并按照AggregatingMergeTree引擎的规则进行处理:

SELECT
    id,
    city,
    sumMerge(value),
    uniqMerge(code)
FROM agg_view
GROUP BY
    id,
city

┌─id───┬─city───┬─sumMerge(value)─┬─uniqMerge(code)─┐
│ A000 │ zhuhai │     400         │        1        │
│ A000 │ wuhan  │     600         │        2        │
└──────┴────────┴─────────────────┴─────────────────┘

 

整个逻辑如下图所示:

 

 

3.3. AggregatingMergeTree总结

AggregatingMergeTree的处理逻辑为:

  1. 使用ORDER BY 排序键作为聚合数据的条件key
  2. 使用AggregateFunction字段类型定义聚合函数的类型以及聚合的字段
  3. 只有在合并分区的时候才会触发聚合计算的逻辑
  4. 以数据分区为单位聚合数据,仅在同一分区内能够进行聚合,无法跨分区进行聚合
  5. 在进行数据计算时,由于分区内的数据已经基于ORDER BY进行排序,所以能够找到那些相邻且拥有相同聚合Key的数据
  6. 在聚合数据时,同一分区内,相同聚合Key的多行数据会合并成一行。对于那些非主键、非AggregateFunction类型字段,则会使用第一行数据的值
  7. AggregateFunction类型的字段使用二进制,在写入数据时需要调用 *State函数;而在查询数据时,需要调用相应的 *Merge 函数。其中 * 表示定义时使用的聚合函数
  8. AggregatingMergeTree通常作为物化视图的表引擎,与普通MergeTree搭配使用

 

4. CollapsingMergeTree

CollapsingMergeTree是支持行级数据修改和删除的引擎。在大数据领域,修改和删除是代价非常高的操作,所以在实现时不会去修改源文件,而是将修改和删除操作转换为新增操作。

CollapsingMergeTree定义了一个sign标记位字段,用于记录数据行的状态:

  • sign为1:表示这是一行有效数据
  • sign为 -1:表示这行数据需要被删除

 

在合并分区时,同一数据分区内,sign标记为1和-1的一组数据会被抵消删除。这种相互抵消的操作,犹如将纸折叠一样,所以这可能也是折叠合并树(CollapsingMergeTree)名称的由来。折叠过程如下图所示:

 

 

4.1. CollapsingMergeTree的使用

建表:

CREATE TABLE collapse_table(
 id String,
 code Int32,
 create_time DateTime,
 sign Int8
 ) ENGINE = CollapsingMergeTree(sign)
PARTITION BY toYYYYMM(create_time)
ORDER BY id

 

同其他MergeTree 引擎一样,CollapsingMergeTree也是通过ORDER BY 排序字段判断数据唯一性;除此之外,CollapsingMergeTree还支持其他2种特殊操作:修改与删除。

下面举例:

--插入原始数据,后续会被修改
INSERT INTO TABLE collapse_table VALUES ('A000', 100, '2019-02-20 00:00:00', 1)

--原始数据的镜像数据,ORDER BY 字段与原条目必须相同(其他字段可以不同),sign取 -1,
--它会和原始条目折叠
INSERT INTO TABLE collapse_table VALUES ('A000', 100, '2019-02-20 10:00:00', -1)

--强制OPTIMIZE 合并后查看结果,可以看到结果为空
SELECT *
FROM collapse_table

0 rows in set. Elapsed: 0.001 sec.

--修改后的数据,sign为1
INSERT INTO TABLE collapse_table VALUES ('A000', 120, '2019-02-20 00:00:00', 1)

--检查结果
SELECT *
FROM collapse_table


┌─id───┬─code─┬─────────create_time─┬─sign─┐
│ A000 │ 120  │ 2019-02-20 00:00:00 │   1  │
└──────┴──────┴─────────────────────┴──────┘

 

CollapsingMergeTree在折叠数据时,遵循以下规则:

  1. 如果sign=1比sign=-1的数据多一行,则保留最后一行sign=1的数据
  2. 如果sign=-1比sign=1的数据多一行,则保留第一行sign=-1的数据
  3. 如果sign=1和sign=-1的数据行一样多,且最后一行为sign=1,则保留第一行sign=-1和最后一行sign=1的数据
  4. 如果sign=1和sign=-1的数据行一样多,且最后一行为sign=-1,则什么也不保留
  5. 其余情况ClickHouse会打印警告日志,但不会报错,此时查询结果未知

 

4.2. CollapsingMergeTree注意事项

1. 折叠数据并非实时触发,而是在分区合并时在进行。所以在分区合并前,旧数据仍对用户可见。解决此问题的方式有2种:

1.1. 在查询数据前,执行 OPTIMIZE TABLE table_name FINAL 强制触发分区合并,但此方法效率很低,在生产环境慎用。

1.2. 改变查询方式。以collapse_table为例,假设原始SQL为:

SELECT id, SUM(code), COUNT(code), AVG(code), uniq(code)

FROM collapse_table

GROUP BY id

 

则可以改写为:

SELECT id, SUM(code * sign), COUNT(code * sign), AVG(code * sign), uniq(code * sign)

FROM collapse_table

GROUP BY id

HAVING SUM(sign) > 0

 

2. 只有相同分区内的数据才可能被折叠,不过删除或修改数据时,一般分区规则都是一致的。

 

3. CollapsingMergeTree最大的限制在于:对于写入数据的顺序有非常严格的要求。前面例子我们可以看到,若是先写sign=1再写sign=-1,则没有任何问题。但若是先写sign=-1再写sign=1 的话呢:

INSERT INTO TABLE collapse_table VALUES('A000', 101, '2019-02-20 00:00:00', -1)

INSERT INTO TABLE collapse_table VALUES('A000', 102, '2019-02-20 00:00:00', 1)

OPTIMIZE TABLE collapse_table FINAL

select * from collapse_table;

┌─id───┬─code─┬─────────create_time─┬─sign─┐
│ A000 │ 101  │ 2019-02-20 00:00:00 │   -1 │
│ A000 │ 102  │ 2019-02-20 00:00:00 │   1  │
└──────┴──────┴─────────────────────┴──────┘

可以看到在顺序置换后,并没有进行折叠。若是在单线程写入时,顺序是可以保证的,但是在多线程并行写入就不一定了。为了解决这个问题,ClickHouse另外提供了一个名为VersionedCollapsingMergeTree的表引擎。

 

5. VersionedCollapsingMergeTree

VersionedCollapsingMergeTree表引擎的功能与CollapsingMergeTree完全相同,不同之处在于:它对数据的写入顺序没有要求,同一分区内,任意顺序的数据都能完成折叠操作。通过版本号实现。

在定义VersionedCollapsingMergeTree时,除了需要指定sign标记字段外,还需要指定一个UInt8类型的ver版本号字段。例如:

CREATE TABLE ver_collapse_table(
 id String,
 code Int32,
 create_time DateTime,
 sign Int8,
 ver UInt8
) ENGINE = VersionedCollapsingMergeTree(sign,ver)
PARTITION BY toYYYYMM(create_time)
ORDER BY id

 

VersionedCollapsingMergeTree是如何使用版本号的呢?很简单,在定义了ver字段后,它会自动将此字段作为排序条件增加到ORDER BY 后面。例如上面定义的表,在每个数据分区内,数据均会以 ORDER BY id, ver DESC 进行排序。所以无论写入时数据的顺序如何,在折叠处理时,都能回到正确的顺序。

例如:

删除一行数据:

INSERT INTO TABLE ver_collapse_table VALUES('A000', 101, '2019-02-20 00:00:00', -1, 1)

INSERT INTO TABLE ver_collapse_table VALUES('A000', 101, '2019-02-20 00:00:00', 1, 1)

OPTIMIZE TABLE ver_collapse_table FINAL

SELECT *
FROM ver_collapse_table

0 rows in set. Elapsed: 0.001 sec.

 

修改数据:

INSERT INTO TABLE ver_collapse_table VALUES('A000', 101, '2019-02-20 00:00:00', -1, 1)

INSERT INTO TABLE ver_collapse_table VALUES('A000', 102, '2019-02-20 00:00:00', 1, 1)

INSERT INTO TABLE ver_collapse_table VALUES('A000', 103, '2019-02-20 00:00:00', 1, 2)

OPTIMIZE TABLE ver_collapse_table;

select * from ver_collapse_table;


┌─id───┬─code─┬─────────create_time─┬─sign─┬─ver─┐
│ A000 │ 103  │ 2019-02-20 00:00:00 │   1  │  2  │
└──────┴──────┴─────────────────────┴──────┴─────┘

 

6. MergeTree家族关系总结

MergeTree表引擎向下派生出6个变种表引擎:

 

 

7. 组合关系

上面是MergeTree关系,下面介绍ReplicatedMergeTree系列。

ReplicatedMergeTree与普通的MergeTree的区别:

 

 

虚线部分是MergeTree的能力边界,而ReplicatedMergeTree是在MergeTree能力的基础之上增加了分布式协同的能力。它借助了ZooKeeper的消息日志广播功能,实现了副本实例之间的数据同步功能。

ReplicatedMergeTree系列通过组合关系来理解,如下图所示:

 

 

在为MergeTree加上Replicated前缀后,又可以组合出7种新的表引擎,这些ReplicatedMergeTree拥有副本协同的能力。之后会详细说明。

 

标签:00,17,id,引擎,2019,table,数据,MergeTree,ClickHouse
来源: https://www.cnblogs.com/zackstang/p/14668944.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有