ToB企服应用市场:ToB评测及商务社交产业平台

标题: ClickHouse(09)ClickHouse合并树MergeTree家族表引擎之MergeTree详细解析 [打印本页]

作者: 罪恶克星    时间: 2022-11-3 19:17
标题: ClickHouse(09)ClickHouse合并树MergeTree家族表引擎之MergeTree详细解析
目录

Clickhouse中最强大的表引擎当属MergeTree(合并树)引擎及该系列(MergeTree)中的其他引擎。MergeTree系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以以数据片段的形式一个接着一个的快速写入,数据片段在后台按照一定的规则进行合并。相比在插入时不断修改(重写)已存储的数据,这种策略会高效很多。
主要特点
建表
  1. CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
  2. (
  3.     name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
  4.     name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],
  5.     ...
  6.     INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
  7.     INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
  8. ) ENGINE = MergeTree()
  9. ORDER BY expr
  10. [PARTITION BY expr]
  11. [PRIMARY KEY expr]
  12. [SAMPLE BY expr]
  13. [TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...]
  14. [SETTINGS name=value, ...]
复制代码
对于建表语句中这个部分的解释.
数据存储

表由按主键排序的数据片段(DATAPART)组成。
当数据被插入到表中时,会创建多个数据片段并按主键的字典序排序。例如,主键是(CounterID,Date)时,片段中数据首先按CounterID排序,具有相同CounterID的部分按Date排序。
不同分区的数据会被分成不同的片段,ClickHouse在后台合并数据片段以便更高效存储。不同分区的数据片段不会进行合并。合并机制并不保证具有相同主键的行全都合并到同一个数据片段中。
数据片段可以以Wide或Compact格式存储。在Wide格式下,每一列都会在文件系统中存储为单独的文件,在Compact格式下所有列都存储在一个文件中。Compact格式可以提高插入量少插入频率频繁时的性能。
数据存储格式由min_bytes_for_wide_part和min_rows_for_wide_part表引擎参数控制。如果数据片段中的字节数或行数少于相应的设置值,数据片段会以Compact格式存储,否则会以Wide格式存储。
每个数据片段被逻辑的分割成颗粒(granules)。颗粒是ClickHouse中进行数据查询时的最小不可分割数据集。ClickHouse不会对行或值进行拆分,所以每个颗粒总是包含整数个行。每个颗粒的第一行通过该行的主键值进行标记,ClickHouse会为每个数据片段创建一个索引文件来存储这些标记。对于每列,无论它是否包含在主键当中,ClickHouse都会存储类似标记。这些标记让您可以在列文件中直接找到数据。
颗粒的大小通过表引擎参数index_granularity和index_granularity_bytes控制。颗粒的行数的在[1,index_granularity]范围中,这取决于行的大小。如果单行的大小超过了index_granularity_bytes设置的值,那么一个颗粒的大小会超过index_granularity_bytes。在这种情况下,颗粒的大小等于该行的大小。
主键和索引在查询中的表现

我们以 (CounterID, Date) 以主键。排序好的索引的图示会是下面这样:
  1.     全部数据  :     [-------------------------------------------------------------------------]
  2.     CounterID:      [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
  3.     Date:           [1111111222222233331233211111222222333211111112122222223111112223311122333]
  4.     标记:            |      |      |      |      |      |      |      |      |      |      |
  5.                     a,1    a,2    a,3    b,3    e,2    e,3    g,1    h,2    i,1    i,3    l,3
  6.     标记号:          0      1      2      3      4      5      6      7      8      9      10
复制代码
如果指定查询如下:
CounterID in ('a','h'),服务器会读取标记号在 [0, 3) 和 [6, 8) 区间中的数据。
CounterID IN ('a','h') AND Date = 3,服务器会读取标记号在 [1, 3) 和 [7, 8) 区间中的数据。
Date = 3,服务器会读取标记号在[1, 10]区间中的数据。
上面例子可以看出使用索引通常会比全表描述要高效。
稀疏索引会引起额外的数据读取。当读取主键单个区间范围的数据时,每个数据块中最多会多读 index_granularity * 2 行额外的数据。
稀疏索引使得您可以处理极大量的行,因为大多数情况下,这些索引常驻于内存。
ClickHouse 不要求主键唯一,所以可以插入多条具有相同主键的行。
可以在PRIMARY KEY与ORDER BY条件中使用可为空的类型的表达式,但强烈建议不要这么做。为了启用这项功能,需要打开allow_nullable_key,NULLS_LAST规则也适用于ORDER BY条件中有NULL值的情况下。
主键的选择

主键中列的数量并没有明确的限制。依据数据结构,可以在主键包含多些或少些列。一般主键的选择可以按照下面的规则:
长的主键会对插入性能和内存消耗有负面影响,但主键中额外的列并不影响SELECT查询的性能。
可以使 ORDER BY tuple()语法创建没有主键的表。在这种情况下ClickHouse根据数据插入的顺序存储。如果在使用INSERT...SELECT时希望保持数据的排序,需要设置 max_insert_threads = 1。
选择与排序键不同的主键

Clickhouse可以做到指定一个跟排序键不一样的主键,此时排序键用于在数据片段中进行排序,主键用于在索引文件中进行标记的写入。这种情况下,主键表达式元组必须是排序键表达式元组的前缀(即主键为(a,b),排序列必须为(a,b,**))。
当使用SummingMergeTree和AggregatingMergeTree引擎时,这个特性非常有用。通常在使用这类引擎时,表里的列分两种:维度和度量。典型的查询会通过任意的GROUP BY对度量列进行聚合并通过维度列进行过滤。由于SummingMergeTree和AggregatingMergeTree会对排序键相同的行进行聚合,所以把所有的维度放进排序键是很自然的做法。但这将导致排序键中包含大量的列,并且排序键会伴随着新添加的维度不断的更新。
在这种情况下合理的做法是,只保留少量的列在主键当中用于提升扫描效率,将维度列添加到排序键中。
对排序键进行ALTER是轻量级的操作,因为当一个新列同时被加入到表里和排序键里时,已存在的数据片段并不需要修改。由于旧的排序键是新排序键的前缀,并且新添加的列中没有数据,因此在表修改时的数据对于新旧的排序键来说都是有序的。
索引和分区在查询中的应用

对于SELECT查询,ClickHouse分析是否可以使用索引。如果WHERE/PREWHERE子句具有下面这些表达式(作为完整WHERE条件的一部分或全部)则可以使用索引:进行相等/不相等的比较;对主键列或分区列进行IN运算、有固定前缀的LIKE运算(如name like 'test%')、函数运算(部分函数适用),还有对上述表达式进行逻辑运算。
因此,在索引键的一个或多个区间上快速地执行查询是可能的。下面例子中,指定标签;指定标签和日期范围;指定标签和日期;指定多个标签和日期范围等执行查询,都会非常快。
  1. --表引擎配置
  2. --ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate) SETTINGS index_granularity=8192;
  3. --
  4. SELECT count() FROM table WHERE EventDate = toDate(now()) AND CounterID = 34
  5. SELECT count() FROM table WHERE EventDate = toDate(now()) AND (CounterID = 34 OR CounterID = 42)
  6. 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'))
复制代码
可用的索引类型

并发数据访问

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

TTL用于设置值的生命周期,它既可以为整张表设置,也可以为每个列字段单独设置。表级别的TTL还会指定数据在磁盘和卷上自动转移的逻辑。
TTL表达式的计算结果必须是日期或日期时间类型的字段。
例如:
  1. SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'
复制代码
列TTL

创建列TTL:
  1. INDEX index_name expr TYPE type(...) GRANULARITY granularity_value
复制代码
在已有的列创建ttl
  1. CREATE TABLE table_name
  2. (
  3.     u64 UInt64,
  4.     i32 Int32,
  5.     s String,
  6.     ...
  7.     INDEX a (u64 * i32, s) TYPE minmax GRANULARITY 3,
  8.     INDEX b (u64 * length(s)) TYPE set(1000) GRANULARITY 4
  9. ) ENGINE = MergeTree()
  10. ...
复制代码
修改列字段的 TTL
  1. SELECT count() FROM table WHERE s < 'z'
  2. SELECT count() FROM table WHERE u64 * i32 == 10 AND u64 * length(s) >= 1234
复制代码
表TTL

表可以设置一个用于移除过期行的表达式,以及多个用于在磁盘或卷上自动转移数据片段的表达式。当表中的行过期时,ClickHouse 会删除所有对应的行。对于数据片段的转移特性,必须所有的行都满足转移条件。
  1. -- TTL time_column
  2. -- TTL time_column + interval
  3. TTL date_time + INTERVAL 1 MONTH
  4. TTL date_time + INTERVAL 15 HOUR
复制代码
TTL 规则的类型紧跟在每个 TTL 表达式后面,它会影响满足表达式时(到达指定时间时)应当执行的操作:
使用WHERE从句,您可以指定哪些过期的行会被删除或聚合(不适用于移动)。GROUP BY表达式必须是表主键的前缀。如果某列不是GROUP BY表达式的一部分,也没有在SET从句显示引用,结果行中相应列的值是随机的(就好像使用了any函数)。
例子:
  1. CREATE TABLE example_table
  2. (
  3.     d DateTime,
  4.     a Int TTL d + INTERVAL 1 MONTH,
  5.     b Int TTL d + INTERVAL 1 MONTH,
  6.     c String
  7. )
  8. ENGINE = MergeTree
  9. PARTITION BY toYYYYMM(d)
  10. ORDER BY d;
复制代码
删除数据

ClickHouse 在数据片段合并时会删除掉过期的数据。
当ClickHouse发现数据过期时,它将会执行一个计划外的合并。要控制这类合并的频率,您可以设置merge_with_ttl_timeout。如果该值被设置的太低,它将引发大量计划外的合并,这可能会消耗大量资源。
如果在两次合并的时间间隔中执行SELECT查询,则可能会得到过期的数据。为了避免这种情况,可以在SELECT之前使用OPTIMIZE。
使用多个块设备进行数据存储

MergeTree 系列表引擎可以将数据存储在多个块设备上。这对某些可以潜在被划分为“冷”“热”的表来说是很有用的。最新数据被定期的查询但只需要很小的空间。相反,详尽的历史数据很少被用到。如果有多块磁盘可用,那么“热”的数据可以放置在快速的磁盘上(比如NVMe固态硬盘或内存),“冷”的数据可以放在相对较慢的磁盘上(比如机械硬盘)。
数据片段是MergeTree引擎表的最小可移动单元。属于同一个数据片段的数据被存储在同一块磁盘上。数据片段会在后台自动的在磁盘间移动,也可以通过ALTER查询来移动。
配置

磁盘、卷和存储策略应当在主配置文件 config.xml 或 config.d 目录中的独立文件中的  标签内定义。
磁盘、卷配置如下,disk_name_N> — 磁盘名,名称必须与其他磁盘不同。path — 服务器将用来存储数据 (data 和 shadow 目录) 的路径, 应当以 ‘/’ 结尾。keep_free_space_bytes — 需要保留的剩余磁盘空间。
  1. ALTER TABLE example_table
  2.     MODIFY COLUMN
  3.     c String TTL d + INTERVAL 1 DAY;
复制代码
存储策略配置如下,policy_name_N — 策略名称,不能重复。volume_name_N — 卷名称,不能重复。
disk — 卷中的磁盘。max_data_part_size_bytes — 卷中的磁盘可以存储的数据片段的最大大小。move_factor — 当可用空间少于这个因子时,数据将自动的向下一个卷(如果有的话)移动 (默认值为 0.1)。prefer_not_to_merge - 禁止在这个卷中进行数据合并。该选项启用时,对该卷的数据不能进行合并。这个选项主要用于慢速磁盘。
  1. ALTER TABLE example_table
  2.     MODIFY COLUMN
  3.     c String TTL d + INTERVAL 1 MONTH;
复制代码
虚拟列

这个介绍mergeTree表的一些虚拟列。
资料分享

ClickHouse经典中文文档分享
参考文章


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4