zoukankan      html  css  js  c++  java
  • ClickHouse介绍(三)MergeTree系列表引擎

    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拥有副本协同的能力。之后会详细说明。

  • 相关阅读:
    DownloadManager
    Android(蓝牙)
    Android下集成Paypal支付
    java 网络(socket)
    java 泛型的几点备忘
    设计模式(模板方法)
    STM32F4_引领入门
    如何查找STM32开发资料
    Keil(MDK-ARM)使用教程(三)_在线调试
    Keil(MDK-ARM)使用教程(二)_菜单
  • 原文地址:https://www.cnblogs.com/zackstang/p/14668944.html
Copyright © 2011-2022 走看看