DBA

ClickHouse表引擎介绍

2020-05-11  本文已影响0人  mysia

ClickHouse表引擎决定了:

1 - MergeTree

在大多数场景中, 我们所使用的引擎主要是 MergeTree 家族。MergeTree适用于高负载任务的最通用和功能最强大的表引擎。这些引擎的共同特点是可以快速插入数据并进行后续的后台数据处理。MergeTree系列引擎支持数据复制(使用Replicated的引擎版本)。

该类型的引擎有:

1.1 - MergeTree

Clickhouse 中最强大的表引擎当属 MergeTree (合并树)引擎及该系列(*MergeTree)中的其他引擎。

MergeTree 引擎系列的基本理念如下。当有巨量数据要插入到表中,要高效地一批批写入数据片段,并希望这些数据片段在后台按照一定规则合并。相比在插入时不断修改(重写)数据进存储,这种策略会高效很多。

主要特点:

注意:Merge引擎不属于MergeTree系列

建表

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS 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]
[SETTINGS name=value, ...]

示例配置:

ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity=8192

示例中,按月分区。同时设置了一个按用户ID哈希的抽样表达式。这让你可以有该表中每个 CounterIDEventDate 下面的数据的伪随机分布。如果你在查询时指定了 SAMPLE 子句。 ClickHouse会返回对于用户子集的一个均匀的伪随机数据采样。

数据存储
表由按主键排序的数据片段组成。

当数据被插入到表中时,会分成数据片段并按主键的字典序排序。例如,主键是(CounterID, Date) 时,片段中数据按 CounterID 排序,具有相同 CounterID的部分按 Date排序。

不同分区的数据会被分成不同的片段,ClickHouse 在后台合并数据片段以便更高效存储。不会合并来自不同分区的数据片段。这个合并机制并不保证相同主键的所有行都会合并到同一个数据片段中。

ClickHouse 会为每个数据片段创建一个索引文件,索引文件包含每个索引行(”标记“)的主键值。索引行号定义为 n * index_granularity。最大的 n 等于总行数除以 index_granularity的值的整数部分。对于每列,跟主键相同的索引行处也会写入”标记“。这些”标记“可以直接找到数据所在的列。

可以只用一单一大表并不断地一块块往里面加入数据 – MergeTree 引擎的就是为了这样的场景。

主键和索引在查询中的表现
(CounterID, Date)为主键。排序好的索引的图示会是下面这样:

全部数据  :     [-------------------------------------------------------------------------]
CounterID:      [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
Date:           [1111111222222233331233211111222222333211111112122222223111112223311122333]
标记:            |      |      |      |      |      |      |      |      |      |      |
                a,1    a,2    a,3    b,3    e,2    e,3    g,1    h,2    i,1    i,3    l,3
标记号:          0      1      2      3      4      5      6      7      8      9      10

如果指定查询如下:

上面例子可以看出使用索引通常会比全表描述要高效。

稀疏索引会引起额外的数据读取。当读取主键单个区间范围的数据时,每个数据块中最多会多读 index_granularity * 2 行额外的数据。大部分情况下,当 index_granularity = 8192时,ClickHouse的性能并不会降级。

稀疏索引能操作有巨量行的表。因为这些索引是常驻内存(RAM)的。

ClickHouse 不要求主键惟一。所以,你可以插入多条具有相同主键的行。

主键的选择
主键中列的数量并没有明确的限制。依据数据结构,应该让主键包含多些或少些列。这样可以:

如果当前主键是 (a, b),然后加入另一个 c列,满足下面条件时,则可以改善性能:
- 有带有 c列条件的查询。
- 很长的数据范围( index_granularity的数倍)里 (a, b)都是相同的值,并且这种的情况很普遍。换言之,就是加入另一列后,可以让你的查询略过很长的数据范围。 

长的主键会对插入性能和内存消耗有负面影响,但主键中额外的列并不影响 SELECT 查询的性能。

选择跟排序键不一样主键
指定一个跟排序键(用于排序数据片段中行的表达式)不一样的主键(用于计算写到索引文件的每个标记值的表达式)是可以的。这种情况下,主键表达式元组必须是排序键表达式元组的一个前缀。

索引和分区在查询中的应用
对于 SELECT 查询,ClickHouse 分析是否可以使用索引。如果 WHERE/PREWHERE 子句具有下面这些表达式(作为谓词链接一子项或整个)则可以使用索引:

因此,在索引键的一个或多个区间上快速地跑查询都是可能的。下面例子中,指定标签;指定标签和日期范围;指定标签和日期;指定多个标签和日期范围等运行查询,都会非常快。

ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate) SETTINGS index_granularity=8192

当这种情况下,这些查询:

SELECT count() FROM table WHERE EventDate = toDate(now()) AND CounterID = 34
SELECT count() FROM table WHERE EventDate = toDate(now()) AND (CounterID = 34 OR CounterID = 42)
SELECT count() FROM table WHERE ((EventDate >= toDate('2014-01-01') AND EventDate <= toDate('2014-01-31')) OR EventDate = toDate('2014-05-01')) AND CounterID IN (101500, 731962, 160656) AND (CounterID = 101500 OR EventDate != toDate('2014-05-01'))

ClickHouse 会依据主键索引剪掉不符合的数据,依据按月分区的分区键剪掉那些不包含符合数据的分区。上文的查询显示,即使索引用于复杂表达式。因为读表操作是组织好的,所以,使用索引不会比完整扫描慢。

下面这个例子中,不会使用索引。

SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'

要检查 ClickHouse 执行一个查询时能否使用索引,可设置force_index_by_dateforce_primary_key。按月分区的分区键是只能读取包含适当范围日期的数据块。这种情况下,数据块会包含很多天(最多整月)的数据。在块中,数据按主键排序,主键第一列可能不包含日期。因此,仅使用日期而没有带主键前缀条件的查询将会导致读取超过这个日期范围。

跳数索引
需要设置 allow_experimental_data_skipping_indices 为 1 才能使用此索引。(执行 SET allow_experimental_data_skipping_indices = 1)。

此索引在 CREATE语句的列部分里定义。

INDEX index_name expr TYPE type(...) GRANULARITY granularity_value

*MergeTree 系列的表都能指定跳数索引。

这些索引是由数据块按粒度分割后的每部分在指定表达式上汇总信息 granularity_value 组成(粒度大小用表引擎里 index_granularity 的指定)。

这些汇总信息有助于用 where 语句跳过大片不满足的数据,从而减少 SELECT 查询从磁盘读取的数据量。

示例

CREATE TABLE table_name
(
    u64 UInt64,
    i32 Int32,
    s String,
    ...
    INDEX a (u64 * i32, s) TYPE minmax GRANULARITY 3,
    INDEX b (u64 * length(s)) TYPE set(1000) GRANULARITY 4
) ENGINE = MergeTree()
...

上例中的索引能让 ClickHouse 执行下面这些查询时减少读取数据量。

SELECT count() FROM table WHERE s < 'z'
SELECT count() FROM table WHERE u64 * i32 == 10 AND u64 * length(s) >= 1234

索引的可用类型

INDEX sample_index (u64 * length(s)) TYPE minmax GRANULARITY 4
INDEX sample_index2 (u64 * length(str), i32 + f64 * 100, date, str) TYPE set(100) GRANULARITY 4
INDEX sample_index3 (lower(str), str) TYPE ngrambf_v1(3, 256, 2, 0) GRANULARITY 4

并发数据访问
应对表的并发访问,使用多版本机制。换言之,当同时读和更新表时,数据从当前查询到的一组片段中读取。没有冗长的的锁。插入不会阻碍读取。对表的读操作是自动并行的。

列和表的TTL
TTL可以设置值的生命周期,它既可以为整张表设置,也可以为每个列字段单独设置。如果TTL同时作用于表和字段,ClickHouse会使用先到期的那个。

被设置TTL的表,必须拥有日期或日期时间类型的字段。要定义数据的生命周期,需要在这个日期字段上使用操作符,例如:

TTL time_column
TTL time_column + interval

要定义interval, 需要使用时间间隔操作符。

TTL date_time + INTERVAL 1 MONTH
TTL date_time + INTERVAL 15 HOUR

列字段 TTL
当列字段中的值过期时, ClickHouse会将它们替换成数据类型的默认值。如果分区内,某一列的所有值均已过期,则ClickHouse会从文件系统中删除这个分区目录下的列文件。

TTL子句不能被用于主键字段。

示例说明:创建一张包含 TTL 的表

CREATE TABLE example_table
(
    d DateTime,
    a Int TTL d + INTERVAL 1 MONTH,
    b Int TTL d + INTERVAL 1 MONTH,
    c String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d;

为表中已存在的列字段添加 TTL

ALTER TABLE example_table
    MODIFY COLUMN
    c String TTL d + INTERVAL 1 DAY;

修改列字段的 TTL

ALTER TABLE example_table
    MODIFY COLUMN
    c String TTL d + INTERVAL 1 MONTH;

表 TTL
当表内的数据过期时, ClickHouse会删除所有对应的行。

示例说明:创建一张包含 TTL 的表

CREATE TABLE example_table
(
    d DateTime,
    a Int
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d
TTL d + INTERVAL 1 MONTH;

修改表的 TTL

ALTER TABLE example_table
    MODIFY TTL d + INTERVAL 1 DAY;

删除数据
当ClickHouse合并数据分区时, 会删除TTL过期的数据。

当ClickHouse发现数据过期时, 它将会执行一个计划外的合并。要控制这类合并的频率, 可以设置 merge_with_ttl_timeout。如果该值被设置的太低, 它将导致执行许多的计划外合并,这可能会消耗大量资源。

如果在合并的时候执行SELECT 查询, 则可能会得到过期的数据。为了避免这种情况,可以在SELECT之前使用 OPTIMIZE查询。

上一篇 下一篇

猜你喜欢

热点阅读