暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

ClickHouse之MergeTree系引擎

DataFlow范式 2019-09-28
1103

Clickhouse提供了很多针对不同场景的存储引擎,每种存储引擎决定了数据的存储方式、查询方式以及是否使用索引等。

Clickhouse的存储引擎分了几大类,每一类包含不同功能的存储方式。笔者主要对使用较多的几种存储引擎重点介绍,后续对不常用的存储引擎再进行介绍。

本篇主要对MergeTree Family进行介绍。

MergeTree Family

对于大多数场景的任务,我们应该使用MergeTree系列中的存储引擎。

MergeTree

MergeTree引擎和该家族的其他引擎(*MergeTree,即以MergeTree结尾的引擎)是最强大的ClickHousе表引擎。

MergeTree家族引擎的基本思想如下:

当有大量的数据需要插入到表中时,最佳合理的方式是将数据快速地依次写入每个part,然后在后台通过一些规则合并这些part。这种方法比在insert时不断重写存储中的数据高效得多。

主要特性:

  • 存储按主键排序的数据 创建一个小的稀疏索引,以帮助更快地查找数据。

  • 如果指定了分区键(PARTITION BY),使用分区 ClickHouse支持使用分区的某些操作,这些分区对具有相同结果的相同数据的一般操作更有效。ClickHouse还会自动裁剪在查询中指定分区键的分区数据,提高了查询性能。

  • 支持数据复制 ReplicatedMergeTree家族的表支持数据复制。

  • 数据采样支持 可以在表中设置数据采样方法。

Merge引擎不属于*MergeTree家族。

创建表

我们来看一下,创建MergeTree表的语法,ClickHouse与标准SQL还是有差异的。

  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. [PARTITION BY expr]

  10. [ORDER BY expr]

  11. [PRIMARY KEY expr]

  12. [SAMPLE BY expr]

  13. [TTL expr]

  14. [SETTINGS name=value, ...]

  • ENGINE 指定引擎名称和引擎的参数。比如ENGINE = MergeTree(),MergeTree没有参数。

  • PARTITION BY 指定分区key。如果分区为month,可以使用 toYYYYMM(date_column)
    表达式, date_column
    为Date类型的日期。

  • ORDER BY 用于排序的key。列的元组或任意表达式。 示例:ORDER BY(CounterID,EventDate)。

  • PRIMARY KEY 如果不同于排序的key,需要指定primary key。 默认情况下,主键与排序key(由ORDER BY子句指定)相同。 因此,在大多数情况下,不必指定单独的PRIMARY KEY子句。

  • SAMPLE BY 抽样的表达式。如果使用了采样表达式,则主键必须包含它。 例: SAMPLE BY intHash32(UserID)ORDER BY(CounterID,EventDate,intHash32(UserID))。

  • TTL 用于设置行存储时间的表达式。 它必须依赖于Date或DateTime类型的列,因此有一个Date或DateTime列作为结果。例如: TTL date + INTERVAL 1 DAY

  • SETTINGS 控制MergeTree行为的其他参数:

    • index_granularity
       索引的粒度,索引标记之间的最大数据行数。 默认情况下, index_granularity=8192

    • 另外还有其他一些参数,比如 enable_mixed_granularity_partsindex_granularity_bytesuse_minimalistic_part_header_in_zookeepermin_merge_bytes_to_use_direct_iomerge_with_ttl_timeout
      等。 如下代码中可以看到所有可用的参数: https://github.com/yandex/ClickHouse/blob/master/dbms/src/Storages/MergeTree/MergeTreeSettings.h

部分配置示例:

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

数据存储

表由按主键排序的每个数据part组成。

这里对数据part进行说明,MergeTree家族的引擎,数据是由多组part文件组成的,比如针对某个分区,可能有几个part文件,其实这里的part就是block的意思。

将数据插入表中时,会创建单独的数据part,并且每个数据part根据主键按字典顺序排序。例如,如果主键是(CounterID,Date),则数据先按CounterID排序,并且在每个CounterID中,按Date排序。

属于不同分区的数据被分成不同的part。在后端,ClickHouse合并这些数据part以实现更高效的存储。属于不同分区的part不会合并。

每个数据part在逻辑上根据粒度(granule)划分。granule是ClickHouse在选择数据时读取的最小的不可分割的数据集。ClickHouse不分割行或值,因此每个granule始终包含整数行数。颗粒的第一行由这一行的主键值标记。对于每个数据部分,ClickHouse创建一个索引文件来存储标记。对于每一列,无论它是否在主键中,ClickHouse也存储相同的标记。这些标记允许直接在列中查找数据。

granule的大小受表引擎的 index_granularity
和 index_granularity_bytes
参数设置的限制。granule中的行数位于 [1,index_granularity]
范围内,这取决于行大小。如果一行的大小大于设置的值,则granule的大小可以超过 index_granularity_bytes
。在本例中,粒度的大小等于行的大小。

  1. 如果 index_granularity=8192
    ,那么就是每隔8192行数据,分为1个block,同时主键会每隔8192行,就取一行主键列的数据,同时记录这是第几个block。

  2. 查询时,如果有索引,就通过索引定位到数据位于那个block,然后找到这个pblock对应的mrk文件,mrk文件里记录的是某个block的数据集,定位到具体的物理偏移量位置,加载数据到内存,然后进行并行化过滤获取数据。

查询中的主键和索引

我们来看(CounterID,Date)主键。 在这种情况下,排序和索引说明如下: 

如果数据查询指定:

  • CounterID in ('a', 'h'),读取marks[0,3]和[6,8]范围内的数据。

  • CounterID IN ('a', 'h') AND Date = 3,读取marks[1,3]和[7,8]范围内的数据。

  • Date = 3,读取marks[1,10]范围内的数据。

上面的示例表明,使用索引总是比完全扫描更有效。

稀疏索引允许读取额外的字符串。当读取单个主键范围时,可以读取每个数据块中的 index_granularity*2
个额外的数据行。 在大多数情况下,当 index_granularity=8192
时,ClickHouse性能不会降低。

稀疏索引可以处理大量的表的数据行,因为这些索引在大多数情况下都存储在内存中。

ClickHouse不需要唯一的主键。 您可以使用相同的主键插入多行。

选择主键

主键中的列数没有明确限制。根据数据结构,选择合适的列作为primary key。

  • 提高索引的性能 如果主键是(a,b),则在满足以下条件时添加另一列c将提高性能:



    • 在列c上存在条件的查询。

    • 具有相同(a,b)值的长数据范围(比 index_granularity
      长几倍),这种情况下,添加另一列时,可以跳过很长的数据范围。

    • <

  • 改善数据压缩。 ClickHouse按主键对数据进行排序,因此数据一致性越高,压缩越好。

  • 在CollapsingMergeTree和SummingMergeTree引擎中合并数据part时提供额外的逻辑 在这种情况下,指定与主键不同的排序键是有意义的。

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

选择不同于排序key的主键

可以指定与排序键(用于对数据part中的行进行排序的表达式)不同的主键(表达式,其值被写入每个mark的索引文件中)。在这种情况下,主键表达式元组必须是排序键表达式元组的前缀。

使用SummingMergeTree和AggregatingMergeTree表引擎时,此特性功能很有用。在使用这些引擎的常见情况下,该表有两种类型的列:维度(dimension)和度量(measure)。典型查询使用任意GROUP BY聚合度量列的值并按维度过滤。

查询中索引和分区的使用

对于SELECT查询,ClickHouse分析判断是否要使用索引。如果WHERE/PREWHERE子句具有表示相等或不等式比较操作的表达式,或者如果它在列或表达式上(在主键或分区键)具有固定前缀的IN或LIKE,就可以使用索引。

可以在主键的一个或多个范围上快速运行查询。在此示例中,针对特定的tracking tag、对于特定tag和date范围、对于特定的tag和date、对于具有date范围的多个tag,依此类推,运行查询会很快。

配置如下的引擎:

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

在这个案例中,查询如下:

  1. SELECT count() FROM table WHERE EventDate = toDate(now()) AND CounterID = 34


  2. SELECT count() FROM table WHERE EventDate = toDate(now()) AND (CounterID = 34 OR CounterID = 42)


  3. 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将使用主键索引来修剪不正确的数据,使用每月分区键来修剪不正确日期范围内的分区。

上面的查询会使用索引,要比全表扫描快。

在下面的示例中,索引没有被使用。

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

要在运行查询时检查ClickHouse是否使用了索引,请使用设置 force_index_by_date
和 force_primary_key

按月分区的key是只允许读取包含适当范围日期的数据块。在这种情况下,数据块可能包含许多日期(最多一个整月)的数据。 在一个数据块中,数据按主键排序,主键可能不包含日期作为第一列。因此,使用仅有日期条件而未指定主键前缀的查询将导致读取比单个日期更多的数据。

并行数据访问

对于并发表的访问,我们使用多版本控制。换句话说,当同时读取和更新表时,从查询时当前的一组part中读取数据,没有长锁,插入不会妨碍读取操作。

从表中读取会自动并行化。

支持字段和表级别的TTL

ClickHouse支持为整个表和每个列设置TTL,即确定值的生命周期。如果两个TTL都设置了,ClickHouse将使用较早过期的TTL。

该表必须具有Date或DateTime数据类型中的列。若要定义数据的生命周期,请使用此时间列上的操作,例如:

  1. TTL time_column

  2. TTL time_column + interval

要定义interval,请使用time interval操作符。

  1. TTL date_time + INTERVAL 1 MONTH

  2. TTL date_time + INTERVAL 15 HOUR

列TTL

当列中的值过期时,ClickHouse用列数据类型的默认值替换它们。如果数据part中的所有列值都过期,ClickHouse将从文件系统中的数据part删除该列。

TTL语句不能用于key字段。

示例:

  1. 创建表时指定

  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;

  1. 在已经存在的表上指定

  1. ALTER TABLE example_table

  2. MODIFY COLUMN

  3. c String TTL d + INTERVAL 1 DAY;

  1. 修改TTL字段

  1. ALTER TABLE example_table

  2. MODIFY COLUMN

  3. c String TTL d + INTERVAL 1 MONTH;

表TTL

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

示例:

  1. 创建表时指定

  1. CREATE TABLE example_table

  2. (

  3. d DateTime,

  4. a Int

  5. )

  6. ENGINE = MergeTree

  7. PARTITION BY toYYYYMM(d)

  8. ORDER BY d

  9. TTL d + INTERVAL 1 MONTH;

  1. 修改表TTL

  1. ALTER TABLE example_table

  2. MODIFY TTL d + INTERVAL 1 DAY;

删除数据

当ClickHouse合并数据part时,带有过期TTL的数据将被删除。

当ClickHouse遇到数据过期时,它会执行本身计划外的合并。要控制此类合并的频率,可以设置 merge_with_ttl_timeout
。如果值设置的太小,它将执行许多计划外的合并操作,这可能会消耗大量资源。

如果在合并之间执行SELECT查询,可能会得到过期数据。要避免这种情况,请在SELECT之前使用OPTIMIZE查询。

  1. OPTIMIZE TABLE [db.]name [ON CLUSTER cluster] [PARTITION partition] [FINAL]

数据复制

ClickHouse的数据复制只支持MergeTree家族的表,包括如下:

  • ReplicatedMergeTree

  • ReplicatedSummingMergeTree

  • ReplicatedReplacingMergeTree

  • ReplicatedAggregatingMergeTree

  • ReplicatedCollapsingMergeTree

  • ReplicatedVersionedCollapsingMergeTree

  • ReplicatedGraphiteMergeTree

复制适用于工作在单个表的级别,而不是整个服务器。服务器可以同时存储复制和非复制表。

复制不依赖于分片。 每个分片都有自己的独立复制。

INSERT 和 ALTER 操作的压缩数据是需要复制的。而CREATE,DROP,ATTACH,DETACH和RENAME操作在单个服务器上执行,不会被复制:

  • CREATE TABLE 在运行操作的服务器上创建新的可复制表。如果此表已存在于其他服务器上,则会添加新副本。

  • DROP TABLE 删除位于运行该操作的服务器上的副本。

  • RENAME在其中一个副本上重命名表。换句话说,复制表可以在不同的副本上具有不同的名称。

要使用复制,请在配置文件中设置ZooKeeper集群的地址。这块内容我在ClickHouse生产集群环境部署时已经介绍过。

示例:

  1. <zookeeper>

  2. <node index="1">

  3. <host>example1</host>

  4. <port>2181</port>

  5. </node>

  6. <node index="2">

  7. <host>example2</host>

  8. <port>2181</port>

  9. </node>

  10. <node index="3">

  11. <host>example3</host>

  12. <port>2181</port>

  13. </node>

  14. </zookeeper>

ZooKeeper版本在3.4.5以及以上版本。

如果未在配置文件中设置ZooKeeper,则无法创建复制表,并且任何现有的复制表都将是只读的。

ZooKeeper不会用在SELECT操作中,因为复制不会影响SELECT的性能,查询的运行速度与非复制表的运行速度一样快。查询分布式复制表时,ClickHouse行为由设置 max_replica_delay_for_distributed_queries
和 fallback_to_stale_replicas_for_distributed_queries
控制。

对于每个INSERT操作,与非复制表相比,可能会导致INSERT的延迟略长。但是,如果遵循建议以每秒不超过一个INSERT的批量插入数据,则不会产生任何问题。用于协调一个ZooKeeper集群的整个ClickHouse集群每秒总共有几百个INSERT。数据插入的吞吐量与非复制数据一样高。

复制是异步和多Master。INSERT(以及ALTER)可以发送到任何可用的服务器。 数据插入在运行操作语句的服务器上,然后将其复制到其他服务器。由于它是异步的,因此最近插入的数据会出现在其他副本上会有一些延迟。
如果部分副本不可用,则数据在可用时写入。如果副本可用,则延迟是通过网络传输压缩数据块所需的时间。

默认情况下,INSERT仅从一个副本等待写入数据的确认。如果数据仅成功写入一个副本,并且具有此副本的服务器宕机或出故障,则存储的数据将丢失。为了允许从多个副本确认数据写入,使用 insert_quorum
选项。

每个数据块都是原子地写入的。INSERT分为最多 max_insert_block_size=1048576
行的块。换句话说,如果INSERT的行少于1048576,则以原子方式生成。

对于相同数据块的多次写入(相同大小的数据块包含相同顺序的相同行),该块仅写入一次。原因是当客户端应用程序不知道数据是否写入数据库时,网络出现故障,因此可以简单地重复INSERT。使用相同的数据发送哪些副本INSERT并不重要。 INSERT是幂等的。重复数据删除参数由merge_tree服务器设置控制。

在复制期间,只有要插入的源数据通过网络传输。进一步的数据转换(合并)以相同的方式在所有副本上进行协调和执行。这样可以最大限度地减少网络使用,这意味着当副本驻留在不同的数据中心时,复制很有效。

可以拥有相同数据的任意数量的副本。Yandex.Metrica在生产中使用双复制。在某些情况下,每台服务器都使用RAID-5RAID-6RAID-10。这是一种相对可靠和方便的解决方案。

系统监视副本上的数据同步性,并在发生故障后能够恢复。故障转移是自动的(对于小的数据差异)或半自动的(当数据差异太大时,这可能预示着配置错误)。

创建复制表

Replicated*MergeTree参数:

  • zoo_path
     在Zookeeper中表的路径

  • replica_name
     在Zookeeper中副本名称

示例:

  1. CREATE TABLE table_name

  2. (

  3. EventDate DateTime,

  4. CounterID UInt32,

  5. UserID UInt32

  6. ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/hits', '{replica}')

  7. PARTITION BY toYYYYMM(EventDate)

  8. ORDER BY (CounterID, EventDate, intHash32(UserID))

  9. SAMPLE BY intHash32(UserID)

如示例所示,这些大括号中的参数可以从如下的配置中取值:

  1. <macros>

  2. <layer>05</layer>

  3. <shard>02</shard>

  4. <replica>example05-02-1.yandex.ru</replica>

  5. </macros>

对于每个复制的表,ZooKeeper中的表的路径应该是唯一的。不同shards上的表应该有不同的路径。在这种情况下,路径由以下部分组成: 常用的前缀是/clickhouse/tables/。我们建议使用这个。

{layer}-{shard}是shard标识符。在本例中,它由两个部分组成,因为Yandex.Metrica集群使用双层shard。对于大多数任务,可以只保留{shard}替换,这将替换为shard标识符。

hits是ZooKeeper中表的节点名。让它与表名相同是一个好主意。它是显式定义的,因为与表名不同,它在重命名查询之后不会更改。

副本名称标识同一表的不同副本。您可以为此使用服务器名,如示例中所示。名称只需要在每个shard中是唯一的。

您可以显式地定义参数,而不是使用替换参数。这对于测试和配置小集群可能很方便。但是,在这种情况下,您不能(ON CLUSTER)使用分布式DDL操作。

在处理大型集群时,我们建议使用替换的方,因为它们可以降低出错的概率。

在每个副本上运行CREATE TABLE查询。这个查询创建一个新的复制表,或者向现有表添加一个新的复制表。

如果在表已经包含其他副本上的一些数据之后添加新的副本,那么在运行查询之后,数据将从其他副本复制到新的副本。换句话说,新的副本与其他副本同步。

若要删除副本,请运行DROP表。但是,只是删除了一个副本——这个副本指的是驻留在运行查询的服务器上的副本。

失败后恢复

如果服务器启动时ZooKeeper不可用,则复制的表切换到只读模式。系统定期尝试连接到ZooKeeper。

如果在INSERT期间ZooKeeper不可用,或者在与ZooKeeper交互时发生错误,则抛出异常。

连接到ZooKeeper后,系统检查本地文件系统中的数据集是否与预期的数据集匹配(ZooKeeper存储此信息)。如果有轻微的不一致,系统通过与副本同步数据来解决它们。

如果系统检测到损坏的数据部分(文件大小错误)或未识别的部分(写入文件系统但未在ZooKeeper中记录的部分),则将它们移动到"detached"子目录(未删除)。任何缺失的部分都是从副本中复制的。

请注意,ClickHouse不执行任何破坏性操作,例如自动删除大量数据。

当服务器启动(或与ZooKeeper建立新的会话)时,它只检查所有文件的数量和大小。如果文件大小匹配,但字节在中间某处发生了更改,则不会立即检测到这一点,但只有在尝试为SELECT查询读取数据时才会检测到这一点。查询抛出一个关于压缩块的非匹配校验和或大小的异常。在这种情况下,数据部分被添加到验证队列中,并在必要时从副本中复制。

如果本地数据集与预期数据集差异太大,就会触发安全机制。服务器在日志中输入此信息并拒绝启动。这样做的原因是,这种情况可能表明配置错误,例如,如果一个shard上的副本被意外地配置为另一个shard上的副本。但是,该机制的阈值设置得相当低,这种情况可能发生在正常故障恢复期间。在这种情况下,数据是通过"pushing a button"半自动恢复的。

要开始恢复,可以在ZooKeeper中创建节点 /path_to_table/replica_name/flags/force_restore_data
,包含任何内容,或者运行命令来恢复所有复制的表:

  1. sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data

然后重启服务器。在启动时,服务器删除这些标志并开始恢复。

数据完全丢失恢复

如果其中一台服务器上的所有数据和元数据都消失了,请按照以下步骤进行恢复:

  1. 在服务器上安装ClickHouse。在包含shard标识符和replica(如果使用的话)的配置文件中正确定义替换。

  2. 如果您有必须在服务器上手动复制的未复制表,则从一个副本(在 /var/lib/clickhouse/data/db_name/table_name/
    目录中)复制它们的数据。

  3. 从一个副本的/var/lib/clickhouse/metadata/位置中复制表定义。如果在表定义中显式地定义了shard或replica标识符,则对其进行更正,使其对应于此副本。(或者启动服务器并执行/var/lib/clickhouse/metadata/中的.sql文件中的所有ATTACH TABLE。)

  4. 要开始恢复,可以使用任何内容创建ZooKeeper节点 /path_to_table/replica_name/flags/force_restore_data
    ,或者运行命令来恢复所有复制的表:

  1. sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data

然后启动服务器(重新启动,如果它已经在运行)。数据将从副本下载。

另一个恢复选项是从ZooKeeper ( /path_to_table/replica_name
)删除关于丢失副本的信息,然后按照"Create replicated tables"中的描述再次创建副本。

恢复期间没有限制网络带宽。如果要同时恢复多个副本,请记住这一点。

把MergeTree转换为ReplicatedMergeTree

我们使用术语MergeTree来指代MergeTree家族中的所有表引擎,与ReplicatedMergeTree相同。

如果您有一个手工复制的MergeTree表,您可以将它转换为一个复制表。如果您已经在一个MergeTree表中收集了大量数据,并且现在想要启用复制,那么可能需要这样做。

如果不同副本上的数据不同,那么首先同步它,或者删除除一个副本之外的所有副本上的数据。

重命名现有的MergeTree表,然后使用旧名称创建一个ReplicatedMergeTree表。将数据从旧表移动到包含新表数据的目录中的"detached"子目录中( /var/lib/clickhouse/data/db_name/table_name/
)。然后在其中一个副本上运行ALTER TABLE ATTACH PARTITION,将这些数据部分添加到工作集中。

把ReplicatedMergeTree转换为MergeTree

创建一个名称不同的MergeTree表。将具有ReplicatedMergeTree表数据的目录中的所有数据移动到新表的数据目录中。然后删除ReplicatedMergeTree表并重新启动服务器。

如果你想在不启动服务器的情况下删除一个ReplicatedMergeTree表:

  • 删除元数据目录(/var/lib/clickhouse/metadata/)中相应的.sql文件。

  • 删除ZooKeeper ( /path_to_table/replica_name
    )中的对应路径。

在此之后,您可以启动服务器,创建一个MergeTree表,将数据移动到其目录,然后重新启动服务器。

当ZooKeeper集群中的元数据丢失或损坏时进行恢复

如果ZooKeeper中的数据丢失或损坏,可以通过将数据移动到上面描述的未复制表来保存数据。

自定义分区键

可以对MergeTree家族表(包括复制表)进行分区。基于MergeTree表的物化视图也支持分区。

分区是表中按指定条件记录的逻辑组合。您可以根据任意的标准设置分区,例如,按月、按天或按事件类型设置分区。每个分区都单独存储,以便简化对这些数据的操作。在访问数据时,ClickHouse只使用尽可能小的分区子集。

在创建表时,分区在PARTITION BY expr指定。分区键可以是表列中的任何表达式。例如,要按月指定分区,可以使用表达式 toYYYYMM(date_column)
:

  1. CREATE TABLE visits

  2. (

  3. VisitDate Date,

  4. Hour UInt8,

  5. ClientID UUID

  6. )

  7. ENGINE = MergeTree()

  8. PARTITION BY toYYYYMM(VisitDate)

  9. ORDER BY Hour


  10. insert into visits select '2019-02-25',8,generateUUIDv4();

分区键也可以是一组表达式(类似于主键)。例如:

  1. ENGINE = ReplicatedCollapsingMergeTree('/clickhouse/tables/name', 'replica1', Sign)

  2. PARTITION BY (toMonday(StartDate), EventType)

  3. ORDER BY (CounterID, StartDate, intHash32(UserID))

  4. SAMPLE BY intHash32(UserID)

在本例中,我们按照当周发生的事件类型设置分区。

在向表中插入新数据时,此数据存储为按主键排序的单独部分(chunk块)。插入之后的10-15分钟内,同一分区的各个部分将合并为整个部分。

合并仅适用于分区表达式具有相同值的数据部分。这意味着您不应该创建过于细粒度的分区(超过1000个分区)。否则,SELECT查询的性能很差,因为文件系统中的文件数量过多,并且打开了文件描述符。

为了查看表的parts和partitions,可以使用system.parts表。 示例:

  1. SELECT

  2. partition,

  3. name,

  4. active

  5. FROM system.parts

  6. WHERE table = 'visits'


partition列包含分区的名称。您可以使用此列值在ALTER ... PARTITION中指定分区名。

name列包含分区数据parts的名称。您可以使用此列在ALTER ATTACH PART 中指定parts的名称。

active列显示parts的状态。

  • 1是active

  • 0是inactive

inactive parts,例如,源parts合并到较大的parts后仍然保留。损坏的数据部分也显示为inactive。

让我们分解第一部分的名称: 201902_1_1_0

  • 201902 分区的名称

  • 1 最小数据块号

  • 1 最大数据块号

  • 0 chunk级别 (它是由合并树的深度形成的)

old-type tables的parts名称为: 20190117_20190123_2_2_0
(最小日期-最大日期-最小块号-最大块号-级别)。

正如您在示例中看到的,同一个分区有几个独立的parts。这意味着这些parts还没有合并。ClickHouse定期合并插入的数据parts,大约在插入后的15分钟内完成。  可以看到合并后,每个分区只有一个part。

此外,还可以使用优化查询执行非计划的合并。例子:

  1. OPTIMIZE TABLE visits PARTITION 201902;

inactive的parts将在合并后大约10分钟内删除。

另一种查看parts和partition集的方法是进入表的目录:/var/lib/clickhouse/data//

/。例如对于我的ClickHouse数据库系统来说:


  1. [root@ckprd1 visits]# ll

  2. total 4

  3. drwxr-x--- 2 clickhouse clickhouse 245 Feb 25 11:12 201901_8_9_1

  4. drwxr-x--- 2 clickhouse clickhouse 297 Feb 25 11:07 201902_1_7_2

  5. drwxr-x--- 2 clickhouse clickhouse 6 Feb 22 17:18 detached

  6. -rw-r----- 1 clickhouse clickhouse 1 Feb 22 17:18 format_version.txt

文件夹 201901_8_9_1”、“201902_1_7_2
是parts的目录。每个part都与相应的分区相关,并且只包含特定月份的数据(本例中的表按月份进行分区)。

“detached”目录包含使用DETACH查询从表中分离的parts。损坏的parts也被移动到这个目录,而不是被删除。服务器不使用“detached”目录中的parts。您可以在任何时候添加、删除或修改这个目录中的数据——服务器在您执行ATTACH查询之前不会访问该数据。

ClickHouse允许对分区进行操作: 删除分区、从一个表复制到另一个表、创建备份。

本篇文章暂时就介绍到这里,接下来会介绍前面提到的MergeTree家族的表,如下:

  • ReplicatedMergeTree

  • ReplicatedSummingMergeTree

  • ReplicatedReplacingMergeTree

  • ReplicatedAggregatingMergeTree

  • ReplicatedCollapsingMergeTree

  • ReplicatedVersionedCollapsingMergeTree

  • ReplicatedGraphiteMergeTree

以及其他存储引擎。

文章转载自DataFlow范式,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论