文章目录
- MergeTree特点
- MergeTree核心参数
- - ORDER BY
- - PARTITION BY
- - PRIMARY KEY
- - SAMPLE BY
- - TTL
- - SETTINGS
- - index_granularity
- - index_granularity_bytes
- - min_index_granularity_bytes
- - enable_mixed_granularity_parts
- - use_minimalistic_part_header_in_zookeeper
- - min_merge_bytes_to_use_direct_io
- - merge_with_ttl_timeout
- - write_final_mark
- - merge_max_block_size
- - storage_policy
- - min_bytes_for_wide_part
- - max_parts_in_total
- - max_compress_block_size
- - min_compress_block_size
- - max_partitions_to_read
- MergeTree分类
- - MergeTree
- - ReplacingMergeTree
- - SummingMergeTree
- - AggregatingMergeTree
- - CollapsingMergeTree
- - VersionedCollapsingMergeTree
- - GraphiteMergeTree
- Replicated*MergeTree
MergeTree特点
- 高效的数据插入与查询性能
- 高写入吞吐量:MergeTree 可以非常高效地处理大量数据的插入,适用于日志采集、大数据实时分析等场景。
- 快速查询:支持按排序键的高效查询,特别适合时间范围查询、主键查找、区间查询等。
- 数据分区 (Partition)
- MergeTree 支持按指定列进行数据分区,分区可以根据时间、ID等字段来进行。通过分区,数据可以被分开存储和管理,从而提高查询性能并有效地管理存储。
- 分区通常用于按时间(如每天、每月)进行管理,特别适用于大规模日志或时序数据。
- 数据删除:可以通过删除整个分区来快速删除不需要的数据,避免逐行删除的低效操作。
- 数据排序 (Sort)
- MergeTree 允许用户定义一个或多个列作为排序键。这决定了数据在物理存储中的排序方式,从而提高了范围查询和点查找的效率。
- 排序键对于查询性能至关重要,因为 MergeTree 可以利用数据排序来加速查询。
- 合并操作 (Merge)
- 自动合并:MergeTree 会定期执行数据合并操作,将多个较小的部分合并为更大的部分。这个过程有助于节省存储空间并提高查询性能。
- 合并策略:数据合并是自动进行的,可以使用系统设置来调整合并的频率和方式。合并操作会清理已删除或更新的数据,保持存储的高效性。
- 数据压缩 (Compression)
- 支持列式存储和高效的压缩算法,使得数据存储更加紧凑,减少磁盘占用。
- 通过适当选择数据类型和压缩设置,可以进一步提升压缩率和存储效率。
- 高可用性和容错
- MergeTree 引擎支持数据副本(replication),可以配置多副本以提高系统的可用性和容错性。即使某个副本失败,查询和写入也不会中断。
- 通过 分布式表(Distributed tables),可以在多个服务器之间分配数据,进一步增强可用性和扩展性。
- 支持索引 (Primary Key and Index)
- MergeTree 支持主键(Primary Key),但是其主键实际上是排序键,不等同于传统关系型数据库中的唯一约束。
- 排序键不仅可以帮助加速查询,还可以在数据合并时提供额外的优化。
- 除了排序键,MergeTree 还支持创建稀疏索引,用于提高查询效率。
- 支持不同的扩展版本
- MergeTree 引擎有多个衍生版本,可以根据具体应用场景选择。例如,ReplacingMergeTree、SummingMergeTree、AggregatingMergeTree等,分别用于特定的需求,如数据去重、聚合等。
- 适应大数据处理
- MergeTree 是 ClickHouse 用来处理大规模数据集的引擎,可以扩展到数十亿条记录并维持高性能的查询与写入速度。
- 支持分布式架构
- MergeTree 支持分布式表,可以将数据分布到不同的物理节点上,提高查询性能并实现负载均衡。
MergeTree核心参数
参数 | 是否必须 | 默认值 | 说明 | 使用示例 |
---|---|---|---|---|
ORDER BY | 是 | – | 定义数据在表中如何排序,影响数据的存储方式和查询性能。 | ORDER BY (event_date, id) |
PARTITION BY | 否 | – | 定义分区方式,决定数据按哪些字段进行物理分区。 | PARTITION BY toYYYYMM(event_date) |
PRIMARY KEY | 否 | – | 等同于 ORDER BY,用于定义数据的存储顺序。 | PRIMARY KEY (event_date, id) |
SAMPLE BY | 否 | – | 定义抽样字段,用于查询时返回数据的子集。 | SAMPLE BY id |
TTL | 否 | – | 定义表中数据的过期时间,超过时间的数据会自动删除。 | TTL event_date + INTERVAL 1 YEAR |
SETTINGS | 否 | – | 设置表的一些内部参数,如合并策略、索引粒度等。 | SETTINGS index_granularity = 8192, max_parts_in_total = 1000 |
- ORDER BY
ORDER BY 参数在 MergeTree 中决定了数据在磁盘上的存储顺序。这个顺序影响查询性能,尤其是范围查询(例如基于日期或其他列的查询)。合理的排序能够使查询更加高效。
ORDER BY 是 MergeTree 中唯一一个必填项,甚至比PRIMARY KEY还重要,因为当用户不设置PRIMARY KEY的情况,很多处理会依照ORDER BY 的字段进行处理。
因此,在不设置PRIMARY KEY的情况下,尽量将主键设置为ORDER BY 字段的前缀字段。比如:ORDER BY 字段是 (id,sku_id) ,id为主键。
注意事项:
- 通常使用查询中频繁的过滤或排序字段。
- ORDER BY 需要至少指定一个字段,通常与分区字段相关。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
ORDER BY (event_date, user_id);
- 在这个例子中,数据将按 event_date 和 user_id 的顺序存储。这有助于优化基于这两个字段的查询,尤其是按日期范围查询时。
- PARTITION BY
PARTITION BY 是 MergeTree 表定义中的一个关键参数,它决定了数据如何被分割成多个分区。每个分区会存储一部分数据,并且每个分区会存储在一个单独的物理文件夹中。这个分区有助于优化查询,尤其是当查询涉及到时间范围或按某个字段过滤时。
PARTITION BY 不是必须的,它是一个可选的参数。你可以选择不使用分区,也可以根据数据的特点和查询需求来决定是否使用分区。如果不指定 PARTITION BY,ClickHouse 会将所有数据存储在一个单一的分区中,这在某些情况下是可行的,特别是当数据量不大或者查询不涉及按时间范围或其他特定字段过滤时。
注意事项:
- 使用分区可以提高基于分区字段的查询性能,但会增加磁盘上的文件数量,过多的分区可能会影响性能。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date Date,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date) -- 按月分区
ORDER BY (id);
- 在上面的例子中,PARTITION BY toYYYYMM(event_date) 表示数据按 event_date 字段的年月进行分区。例如,所有 2024 年 1 月的数据将存储在一个名为 202401 的分区中,2024 年 2 月的数据存储在 202402 的分区中。
- PRIMARY KEY
PRIMARY KEY 在 MergeTree 中的作用与索引类似,但它并不强制唯一性约束。它决定了表中数据的存储顺序。
默认情况下主键跟排序键(ORDER BY)相同,所以下面常说主键(ORDER BY),不要误解了。大部分情况下不需要再专门指定一个 PRIMARY KEY,但如果要选择与排序键不同的主键,就可以通过PRIMARY KEY指定。
注意事项:
- PRIMARY KEY 字段通常是查询时经常使用的字段。
- PRIMARY KEY 不保证数据的唯一性,它只是定义了数据的存储顺序。
使用示例:
REATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
PRIMARY KEY (event_date, user_id);
- 此例中,PRIMARY KEY 与 ORDER BY 一致,表示数据按 event_date 和 user_id 的顺序存储。
- SAMPLE BY
SAMPLE BY 参数用于在查询时执行数据抽样。它基于某个字段的值来抽取数据,适用于需要快速返回部分数据的场景,例如大规模数据的估算或分析。
注意事项:
- 通常与较大的数据集配合使用,以减少查询时的负载。
- SAMPLE BY 仅在查询时有效,不会影响数据存储。
- 如果要用SAMPLE BY,SAMPLE BY的字段必须包含在主键中
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SAMPLE BY user_id;
- 在这个例子中,查询时会基于 user_id 字段进行抽样,而不是扫描整个表。
- TTL
TTL 参数用于设置数据的过期时间。通过定义 TTL,可以自动删除过期的数据,常用于日志数据、时间序列数据等。
注意事项:
- 支持基于某个字段(如时间字段)设置过期时间。
- 可以设置数据的删除规则,也可以配置数据的过期清理策略。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
TTL event_date + INTERVAL 1 YEAR; -- 数据1年后过期
- TTL event_date + INTERVAL 1 YEAR 表示表中数据在插入后 1 年自动过期并删除。
- SETTINGS
SETTINGS 参数用于设置表的一些内部参数,如合并策略、索引粒度等。通过合理设置这些参数,可以优化性能、资源利用率、存储效率等方面。
常见参数:
参数名 | 描述 | 默认值 |
---|---|---|
index_granularity | 索引粒度,控制每个数据块内的最大行数。较低的值会增加查询速度,但也会增加存储空间的使用。 | 8192 |
index_granularity_bytes | 索引粒度,指定以字节为单位的索引粒度。控制每个数据块大小,通常用于调整数据块大小以提高查询性能。 | 10485760(10MB) |
min_index_granularity_bytes | 最小索引粒度,控制最小的索引粒度。通过此参数可以避免过小的索引粒度影响性能。 | 1024(1KB) |
enable_mixed_granularity_parts | 是否启用混合粒度的部分。在某些情况下,启用混合粒度的分区可以提高查询效率,尤其是对于较大行的查询。 | 1 |
use_minimalistic_part_header_in_zookeeper | 是否使用简化的部分头文件来减少 ZooKeeper 中的存储空间。如果启用,它将减少每个数据部分的元数据大小。 | 0 |
min_merge_bytes_to_use_direct_io | 在合并操作时,如果某个数据块的大小超过此阈值,则使用直接 I/O 来提高性能。 | 10737418240(10GB ) |
merge_with_ttl_timeout | TTL 合并操作的最小时间间隔,单位:秒。避免过于频繁的合并操作。 | 14400 (4小时) |
write_final_mark | 是否启用在数据段末尾写入最终标记。在某些情况下,写入最终标记可以优化查询性能。 | 1 |
merge_max_block_size | 在合并过程中,最大块大小限制。较大的值可能会提高合并操作的效率,但可能会影响性能。 | 8192 |
storage_policy | 存储策略,用于配置不同存储设备的使用。例如,可以设置数据在不同设备上的分布策略。 | |
min_bytes_for_wide_part | 当分区的数据块大小超过此阈值时,使用 Wide 格式进行存储。适用于需要存储大量列数据的场景。 | 10MB |
max_parts_in_total | 每个表可以有的最大分区数量。限制分区数量以避免过多分区导致性能下降。 | 100000 |
max_compress_block_size | 数据压缩时每个数据块的最大大小,控制压缩过程中的块大小。较大的块可以提高压缩效率,但可能影响性能。 | 1048576(1MB) |
min_compress_block_size | 数据压缩时每个数据块的最小大小。较小的块可能会增加压缩开销。 | 65536(64KB) |
max_partitions_to_read | 每次查询中可以读取的最大分区数。增加此参数的值可以增加查询的并行度,但可能影响性能。 | -1 |
- index_granularity
index_granularity 控制着数据的索引粒度。这个参数决定了每次索引包含多少数据行。较小的粒度意味着更多的索引文件和较快的查询响应,但也增加了存储开销。
注意事项:
- 较小的粒度有助于提高查询性能,尤其是在大数据量时。
- 默认值为 8192,通常适用于大多数场景。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS index_granularity = 8192;
- index_granularity = 8192 设置了索引的粒度为 8192,适用于大多数场景。
- index_granularity_bytes
指定索引粒度的大小,以字节为单位。通过调整此参数,可以控制每个数据块的大小,进而优化存储和查询性能。较大的值将减少索引文件的数量,但可能影响查询的响应时间。
注意事项:
- 默认值为 10MB,适合大多数场景。
- 较大值可能提高查询性能,但会增加内存使用。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS index_granularity_bytes = 10485760;
- index_granularity_bytes = 10485760,设置了每个数据块的最大大小为 10MB,优化了查询性能。
- min_index_granularity_bytes
设置最小索引粒度的大小,单位为字节。它限制了每个数据块最小的字节数,避免过小的索引块导致不必要的性能开销。
注意事项:
- 默认值为 1KB,通常适用于数据量较小的情况。
- 调整此参数时需要权衡存储空间与查询效率。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS min_index_granularity_bytes = 1024;
- min_index_granularity_bytes = 1024,确保每个索引数据块至少为 1KB,避免过小的块影响查询效率。
- enable_mixed_granularity_parts
控制是否启用混合粒度的数据部分。启用后,ClickHouse 在不同的数据部分中使用不同的粒度,以提高查询效率。
注意事项:
- 默认1启用,适用于大多数情况下。
- 在特定场景下禁用可以避免潜在的性能问题。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS enable_mixed_granularity_parts = 1;
- enable_mixed_granularity_parts = 1,启用了混合粒度的分区。
- use_minimalistic_part_header_in_zookeeper
决定是否使用简化的部分头文件以减少 ZooKeeper 中存储的元数据大小。启用该选项有助于减少元数据存储空间的占用。
注意事项:
- 启用后会减少 ZooKeeper 中每个数据部分的元数据大小,但可能影响系统的灵活性。
- 默认值为0否,可以根据需求启用。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS use_minimalistic_part_header_in_zookeeper = 1;
- use_minimalistic_part_header_in_zookeeper = 1,启用了简化的部分头文件,以减少 ZooKeeper 的元数据开销。
- min_merge_bytes_to_use_direct_io
在合并过程中,如果某个数据块的大小超过此阈值,则会使用直接 I/O,这可以显著提高大数据量合并操作的性能。
注意事项:
- 默认值为 10GB,适用于大规模的数据合并操作。
- 使用直接 I/O 可以避免操作系统的缓存,从而提高效率,但也可能增加硬件负载。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS min_merge_bytes_to_use_direct_io = 10737418240;
- min_merge_bytes_to_use_direct_io = 10737418240 设置了合并过程中,使用直接 I/O 的最小数据块大小为 10GB。
- merge_with_ttl_timeout
指定 TTL 合并操作的最小时间间隔,单位为秒。此参数帮助避免 TTL 操作频繁触发合并操作,减少系统负担。
注意事项:
- 默认值为14400(4小时),适用于大多数场景。
- 调整此参数可以平衡数据的清理效率与合并操作的负载。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS merge_with_ttl_timeout = 86400;
- merge_with_ttl_timeout = 86400,设置了 TTL 合并操作最小的时间间隔为 1天。
- write_final_mark
控制是否在每个数据段的末尾写入最终标记。启用该功能有助于优化查询性能,尤其是对大数据集的查询。
注意事项:
- 默认1启用。
- 禁用可能会略微降低查询性能。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS write_final_mark = 1;
- write_final_mark = 1,启用了最终标记的写入,优化了查询性能。
- merge_max_block_size
在合并操作时,指定最大数据块的大小。较大的块可能会提高合并操作的效率,但也可能对性能产生影响,尤其是在资源有限的情况下。
注意事项:
- 默认为 8192,适用于大多数场景。
- 根据硬件资源调整此参数,以避免性能瓶颈。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS merge_max_block_size = 8192;
- merge_max_block_size = 8192,设置了每个数据块的最大大小。
- storage_policy
指定存储策略,定义不同存储设备的使用。例如,可以设置不同的数据表在不同存储设备上的分布策略,以便根据数据的重要性和使用频率进行优化。
注意事项:
- 默认情况下没有设置。
- 存储策略的定义和使用可以显著提高数据存储和查询的效率。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS storage_policy = 'default';
- storage_policy = ‘default’,设置了使用默认的存储策略。
- min_bytes_for_wide_part
设置在合并操作中,最小数据块大小,决定何时使用宽列格式(Wide Part)。当数据块大小大于该阈值时,ClickHouse 会选择使用宽列格式存储数据。这对于列存储优化尤为重要,可以提高大数据量查询时的性能。
注意事项:
- 默认值为 1048576(1MB),适用于大多数情况。
- 如果数据量较小,可以考虑调整该值,避免使用宽列格式,从而节省存储空间和计算开销。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS min_bytes_for_wide_part = 1048576;
- min_bytes_for_wide_part = 1048576,设置了合并时使用宽列格式的最小数据块大小为 1MB。
- max_parts_in_total
max_parts_in_total 控制表中允许的最大分区数量。如果表中的分区数量超过此限制,ClickHouse 将自动触发合并操作以减少分区数量。
注意事项:
- 默认100000,使用此参数可以限制表中分区的数量,避免过多分区导致性能下降。
- 合并操作是后台自动执行的。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS max_parts_in_total = 1000;
- max_parts_in_total = 1000,表示如果分区数量超过 1000 个,ClickHouse 会自动进行合并操作。
- max_compress_block_size
用于控制 ClickHouse 在压缩数据时,单个数据块的最大压缩大小。该参数设置了压缩过程中单个块的最大字节数,当压缩数据块的大小达到该限制时,ClickHouse 会停止压缩该块,开始对下一个数据块进行压缩。
注意事项:
- 默认值:默认情况下,max_compress_block_size 的值1MB。如果没有特别需求,默认值通常已经足够。
- 大块压缩:设置较大的 max_compress_block_size 可以减少压缩操作的频繁度,但可能会占用更多的内存。如果系统内存不足,可能导致性能下降或内存溢出。
- 小块压缩:设置较小的 max_compress_block_size 可以减少内存占用,但也可能导致压缩操作过于频繁,从而影响性能。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS max_compress_block_size = 2097152;
- max_compress_block_size = 2097152 设置了压缩块的最大大小为 2MB。如果单个数据块的压缩大小达到该限制,ClickHouse 将停止对该块的压缩。
- min_compress_block_size
用于控制压缩操作的一个配置参数。它设置了在进行数据压缩时,单个数据块的最小字节数。当数据块的大小小于该值时,ClickHouse 会尝试将多个较小的数据块合并为一个更大的块进行压缩。该参数与 max_compress_block_size 共同决定了压缩块的大小范围。
注意事项:
-
默认值:min_compress_block_size 的默认值通常为 64KB,当数据块的大小小于64KB时,ClickHouse 会尝试将多个较小的数据块合并为一个更大的块进行压缩。
-
合并小块:如果设置了一个较大的 min_compress_block_size,那么即便某些数据块较小,ClickHouse 也会尝试将它们与其他块合并,以满足最小大小的要求。反之,若设置较小的 min_compress_block_size,那么数据块的合并会更加频繁,可能导致更小的压缩块。
-
性能和内存消耗平衡:过小的压缩块可能会导致压缩过程频繁启动,增加 CPU 和内存开销;而过大的压缩块则可能导致较大的内存使用和磁盘I/O开销。因此,合理调整该参数可以帮助平衡性能和资源消耗。
使用示例:
CREATE TABLE example
(
id UInt32,
event_date DateTime,
user_id UInt32,
value Decimal(10, 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, user_id)
SETTINGS min_compress_block_size = 4194304; -- 4MB
- min_compress_block_size = 4194304,设置了压缩块的最小大小为 4MB。如果单个数据块的大小低于 4MB,ClickHouse 会尝试将多个小块合并为一个较大的压缩块。
- max_partitions_to_read
控制查询时可以同时读取的最大分区数量。它的作用是限制在一个查询中,能够同时读取的 分区(partitions)数量,避免系统因读取过多分区而导致过高的负载或性能问题。
注意事项:
- 默认值:max_partitions_to_read 的默认值通常是 -1,即默认情况下,不限制一次查询的分区数。
- 调整范围:可以根据实际的硬件资源、查询特性和数据分布情况来调整该参数。对于大规模的分区表,可以适当增大该值;而对于资源有限或查询负载较高的系统,可以考虑将该值设置为较低的数值,以避免性能问题。
使用示例:
SELECT *
FROM events
WHERE event_date BETWEEN '2024-01-01' AND '2024-01-31'
SETTINGS max_partitions_to_read = 50;
- max_partitions_to_read = 50,说明查询只会读取最多 50 个分区。即使表的数据分区数远超过 50,ClickHouse 也会限制扫描的分区数量,确保查询不会因为过多分区而消耗过多资源。
MergeTree分类
MergeTree 是 ClickHouse 中最常用的存储引擎,适用于大规模的数据仓库。它在性能和灵活性方面表现优秀,支持并行读取和合并操作,支持索引和分区,地位可以相当于 innodb 之于 Mysql。
引擎类型 | 描述 | 特点 |
---|---|---|
MergeTree | 基础的存储引擎,支持高效的数据插入与查询 | 支持数据分区、排序与索引,适用于大多数场景,如日志、时间序列数据 |
ReplacingMergeTree | 支持行级替换,可以通过版本号标记来替换数据 | 支持根据版本号字段替换行,适用于去重或更新操作,如数据覆盖 |
SummingMergeTree | 支持对数值列进行求和,合并相同分区和排序键的行 | 在合并过程中对数值列进行求和,适用于聚合型数据,如计数、求和 |
AggregatingMergeTree | 允许使用聚合函数对数据进行预聚合 | 在数据插入时使用聚合函数进行预计算,适用于实时聚合分析 |
CollapsingMergeTree | 支持通过符号列(sign)标记数据删除,合并时处理删除标记 | 根据符号列(如删除标记)合并数据,适用于删除行为数据 |
VersionedCollapsingMergeTree | 结合版本管理与删除标记,处理版本和删除数据 | 支持版本管理和删除标记的合并,适用于历史记录和审计日志 |
GraphiteMergeTree | 针对 Graphite 模型优化的存储引擎 | 专为存储和查询时间序列数据优化,支持分区、排序和压缩等特性,适用于时序数据存储 |
- MergeTree
ClickHouse中最基本的存储引擎。它为高性能查询和数据插入提供了基础支持。
工作原理:
将数据按照主键排序存储,以便在查询时快速定位和读取数据。当插入新数据时,MergeTree会将数据追加到一个临时的未排序区域。然后,后台的合并进程会定期将这些未排序的数据块与已排序的数据块合并,以保持数据的有序性。
使用场景:
需要高性能查询和数据插入的应用、数据按照主键排序存储、数据更新和删除操作较少。
优缺点:
- 优点:高性能查询(由于数据按照主键排序存储,可以快速定位和读取数据)
- 优点:高性能插入(支持高速数据插入,因为新数据会先追加到未排序区域,然后在后台进行合并)
- 缺点:不支持实时更新和删除、不支持分布式和高可用性。
- ReplacingMergeTree
在MergeTree中,虽然有主键,但是它没有唯一键的约束。也就是说,写入数据的主键是可以重复的。尽管 MergeTree 可以设置primary key ,但是clickhouse中的primary key其实没有唯一约束的功能。
如果你想处理掉重复的数据,可以借助这个 ReplacingMergeTree。但在某些场合下,用户不希望表中有重复数据,ReplacingMergeTree就是为此场景而设计,它可以在合并分区时,删除重复的数据条目。不过此方法仅是在“一定程度”上解决了重复数据的问题。
ReplacingMergeTree 的基本思想是在合并(Merge)过程中根据某些列的值来“替换”旧的数据。合并是 ClickHouse 的一种背景操作,它会将多个小的部分数据文件合并成更大的文件,以提高查询性能和减少存储空间。
合并操作:
- 去重时机:
数据的去重只会在合并的过程中出现。合并会在未知的时间在后台进行,所以你无法预先作出计划。有一些数据可能仍未被处理。 - 去重范围:
如果表经过了分区,去重只会在分区内部进行去重,不能执行跨分区的去重。
所以 ReplacingMergeTree 能力有限, ReplacingMergeTree 适用于在后台清除重复的数据以节省空间,但是它不保证没有重复的数据出现。
特点:
- 使用ORDER BY排序键作为判断重复的唯一键
- 数据的去重只会在合并的过程中触发
- 以数据分区为单位删除重复数据,不同分区的的重复数据不会被删除
- 找到重复数据的方式依赖数据已经ORDER BY排好序了
- 数据去重策略有两种:
- 如果没有设置 ver 版本号,则保留同一组重复数据中的最后一条;
- 如果设置了 ver 版本号,则保留同一组重复数据中 ver 字段取值最大的那一行
使用ReplacingMergeTree 引擎时,是否指定列会影响数据的替换逻辑。具体来说,ReplacingMergeTree 允许你指定一个列(通常是时间戳或其他排序依据列)来确定数据是否需要替换,如果不指定列,则替换的行为会有所不同。
-
指定列的 ReplacingMergeTree
如果指定了列,ClickHouse 会使用这个列来决定数据的“替换”顺序。通常,指定的列用于判断哪一行是“最新”的数据。通常用于时间戳或版本号列,这样最新的数据行会被保留,旧的数据会被替换。
示例:CREATE TABLE example_table ( id Int32, name String, value Float64, timestamp DateTime ) ENGINE = ReplacingMergeTree(timestamp) ORDER BY id;
在这个示例中,timestamp 被指定为替换依据列。
- 替换逻辑:当合并操作发生时,ClickHouse 会根据 timestamp 列的值来确定保留哪条记录。如果有多条记录具有相同的 id,则会保留 timestamp 值最大的记录,即最新的数据。
- 作用:如果数据的主键(ORDER BY)相同,但 timestamp 不同,则保留 timestamp 最大的记录,其它的记录会被合并掉。
-
不指定列的 ReplacingMergeTree
不指定替换列,则默认的行为是通过表的 主键(ORDER BY) 来决定数据的替换。通常情况下,ClickHouse 会根据表的主键列来判断记录是否重复,但不会进行任何基于其他列(如时间戳或版本号)的替换逻辑。
示例:CREATE TABLE example_table ( id Int32, name String, value Float64 ) ENGINE = ReplacingMergeTree() ORDER BY id;
在这个示例中,ReplacingMergeTree 引擎没有指定替换列,因此替换操作基于表的 ORDER BY id 列进行。
- 替换逻辑:如果有多条记录具有相同的 id,系统会选择一个作为代表,而不根据任何其他列(如时间戳)来判断哪条记录是最新的。最终哪条记录会被保留,通常是按照插入顺序或者合并策略决定。
- 效果:没有指定替换列时,ClickHouse 仅依赖于表的主键(ORDER BY )来处理重复数据,因此它无法像指定列那样按照时间或版本判断最新的数据。
总结:
- ReplacingMergeTree 主要是根据 ORDER BY 来判断记录的重复性,而不是通过传统的 PRIMARY KEY。
- 如果指定了替换列(如 timestamp),ClickHouse 会使用这个列来确定哪些记录是重复的,并保留最新的记录
- 如果没有指定替换列,则仅根据 ORDER BY 中的列来决定哪条记录保留。
- SummingMergeTree
SummingMergeTree 是一种特殊的 MergeTree 引擎,专门用于对某些列进行求和操作。它通常用于处理那些需要根据某些条件对数据进行聚合的场景。
SummingMergeTree 在 MergeTree 的基础上,增加了对数据在合并过程中进行聚合的功能,尤其是对数值型数据进行求和。它可以帮助你在存储大量数据时,自动对重复的记录进行聚合,从而减少存储空间,同时提升查询性能。
在 SummingMergeTree 中,当表中的记录合并时,SummingMergeTree 会根据 ORDER BY 中的列来判断是否存在重复的行。如果行的 ORDER BY 列相同,则合并时会对指定的数值列进行求和。。这种聚合过程只发生在 合并 阶段,而不在插入数据时。
- ORDER BY:决定了数据的排序顺序,也决定了合并时如何判断哪些记录是重复的。在合并过程中,ORDER BY 列的值相同的记录会进行聚合。
- 聚合列:在 SummingMergeTree 中,数值类型的列会被求和。如果你定义了多个聚合列,这些列会在合并时根据列值进行累加。
使用场景:
- 需要对某些列进行求和:例如,日志记录、销售记录、计量数据等。
- 重复记录需要合并:例如,按用户 ID 聚合某些数据(例如,用户的消费金额)。
- 提高查询效率:通过合并相同的记录,可以减少存储空间,同时提升查询时的性能。
语法格式:
CREATE TABLE table_name
(
column1 DataType,
column2 DataType,
value_column1 Int64,
value_column2 Int64,
...
)
ENGINE = SummingMergeTree
(
value_column,value_column2 -- 数据列
)
ORDER BY (column1, column2);
在 ClickHouse 中,SummingMergeTree 引擎的分组列和 ORDER BY 列是密切相关的,但它们并不完全相同:
-
分组列(即聚合的依据)
- 分组列用于定义数据如何聚合(合并)。在 SummingMergeTree 引擎中,指定哪些列需要进行聚合时,这些列就是所谓的分组列。ClickHouse 会根据这些列的值来决定如何将数据行合并,并对数值列进行聚合(如求和)。
- 如果没有显式指定分组列,ClickHouse 会默认将 ORDER BY 中的列作为分组列来进行聚合操作。
-
数据列(参与聚合的列)
- 如果没有指定数据列,默认会对数值类型的列进行聚合
- 如果有指定数据列(必须是数值类型),会对指定的数据列进行聚合
特点:
- 分组列:以指定的分组列(默认使用排序列:ORDER BY)作为分组列
- 数据列:以指定的数据列(如果未指定,默认使用所有数值列)作为数据列
- 其他列:剩余其他的列按插入顺序保留第一行
- 同一批次插入或分片合并时才会触发聚合
- 不在一个分区的数据不会被聚合
使用示例:
有一个销售记录表,包含以下字段:user_id(用户 ID)、product_id(产品 ID)、amount(消费金额):
CREATE TABLE sales
(
user_id UInt32,
product_id UInt32,
amount Float64
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(now()) -- 按月份分区
ORDER BY (user_id, product_id); -- 按用户 ID 和产品 ID 排序
- ORDER BY (user_id, product_id):表示记录将按 user_id 和 product_id 排序。
- amount 列将会在合并时求和:如果某个用户购买了相同的产品多次,SummingMergeTree 会在合并时将 amount 累加,存储为该用户对该产品的总消费金额。
当插入新的数据时,SummingMergeTree 不会立即对这些数据进行求和。相反,它会根据排序规则将数据存储在磁盘上。只有在执行合并操作时(通常是后台自动进行的),数据才会按照 ORDER BY 中的列进行合并,并对数值列进行求和。
例如,如果插入了以下两条记录:
INSERT INTO sales VALUES (1, 1001, 50.0);
INSERT INTO sales VALUES (1, 1001, 30.0);
这两条记录的 user_id 和 product_id 相同,因此在合并时,ClickHouse 会将 amount 列的值合并,最终只存储一条记录:
user_id = 1, product_id = 1001, amount = 80.0
- AggregatingMergeTree
AggregatingMergeTree 是一种特殊MergeTree引擎,适用于需要对数据进行聚合操作的场景,如计数、求和、最大值/最小值等。它结合了 MergeTree 引擎的高效存储特性和聚合操作的功能,能够在数据写入时进行实时聚合,从而极大地提升查询性能,尤其适合大规模数据分析场景。
与 SummingMergeTree 类似,AggregatingMergeTree 在数据存储时并不立即执行标准的合并操作,而是对一些列进行预聚合,数据合并的操作发生在查询时,而非数据插入时。这样可以显著减少查询时间,尤其是在需要对大量数据进行聚合计算时。
聚合类型:
- AggregatingMergeTree 允许使用聚合函数来对表中的数据进行处理,通常会使用以下两类聚合函数:
- 通用聚合函数:如 sum(), avg(), count(), min(), max() 等。
- 状态聚合函数:这些聚合函数使用 AggregateFunction 类型来跟踪状态(如:sumState, countState, minState, maxState 等)。这些函数不返回最终结果,而是返回聚合的中间状态,查询时再计算结果。
AggregatingMergeTree 中,表的结构与常规 MergeTree 引擎类似,但通常会包含一些 AggregateFunction 类型的列。这些列用于存储聚合的中间状态。
特点:
- 在 AggregatingMergeTree 中,数据的最终聚合计算是延迟的,只有在查询时才会进行。这种设计模式有助于提高数据插入时的性能,同时确保查询时能够获得正确的聚合结果。
- 当 ClickHouse 需要对表中的数据进行合并时,它会基于聚合状态(例如 sumState, countState)来合并数据。在查询时,使用 sumMerge, countMerge 等函数对这些中间状态进行合并,得到最终的聚合结果。
AggregatingMergeTree 与其他引擎的比较:
特性 | AggregatingMergeTree | SummingMergeTree | MergeTree |
---|---|---|---|
聚合类型 | 支持多种聚合类型 | 仅支持求和 | 不支持聚合 |
聚合状态存储 | 支持聚合状态存储 | 不支持聚合状态存储 | 不支持聚合 |
聚合延迟 | 查询时计算聚合结果 | 插入时计算聚合结果 | 不涉及聚合 |
使用场景 | 大规模复杂聚合计算 | 简单的求和计算 | 通用的存储引擎 |
使用场景:
- 实时数据聚合:例如,日志分析、实时监控、用户行为分析等场景。
- 高效的复杂聚合计算:当数据量很大并且需要在查询时进行复杂的聚合时,AggregatingMergeTree 能提供非常好的性能。
- 需要多种聚合函数支持的应用:如果需要使用不同的聚合类型(如求和、计数、最大值、最小值等),AggregatingMergeTree 是非常合适的。
使用示例:
CREATE TABLE user_activity
(
user_id UInt32,
activity_type String,
total_time AggregateFunction(sum, Float64), -- 使用状态聚合函数
event_count AggregateFunction(count, UInt32) -- 使用状态聚合函数
)
ENGINE = AggregatingMergeTree
ORDER BY (user_id, activity_type);
- user_id 和 activity_type 是普通的数据列,用于标识用户和活动类型。
- total_time 和 event_count 是 AggregateFunction 类型的列,分别用于存储时间总和和事件数量的中间状态。
插入数据:
在插入数据时,并不直接执行聚合,而是插入聚合状态。这意味着需要使用聚合函数来生成聚合的中间状态,并将其插入到表中
INSERT INTO user_activity
SELECT
user_id,
activity_type,
sumState(total_time) AS total_time,
countState(event_count) AS event_count
FROM raw_user_activity_data
GROUP BY user_id, activity_type;
在这个插入操作中,sumState(total_time) 和 countState(event_count) 用于计算聚合的中间状态,并插入到 user_activity 表中。
查询数据:
查询时,AggregatingMergeTree 会根据中间状态进行最终的聚合计算。ClickHouse 会在查询时自动计算聚合的结果,返回最终的值。
SELECT
user_id,
activity_type,
sumMerge(total_time) AS total_time,
countMerge(event_count) AS event_count
FROM user_activity
GROUP BY user_id, activity_type;
在查询时,sumMerge(total_time) 和 countMerge(event_count) 会对插入的中间聚合状态进行合并和最终计算。
- CollapsingMergeTree
CollapsingMergeTree 是一种特殊MergeTree引擎,主要用于支持 删除操作 或 数据消除 的场景。它是 MergeTree 引擎的一种扩展,允许用户在数据插入时标记哪些行是“删除”操作(或“标记为无效”)。通过这种方式,CollapsingMergeTree 可以高效地管理这些“标记删除”的行,并在查询时自动处理这些行,避免不必要的数据计算。
CollapsingMergeTree 通过在表中引入一个额外的 标记列(sign column) 来实现数据的“删除”或“消除”。这个标记列通常用于区分有效数据和无效数据(或已删除数据)。通常会用 1 和 -1 来表示数据的不同状态:
- 1 表示数据是有效的。
- -1 表示数据是无效的,或者说是被标记为删除的。
当有相同的记录(同一主键)插入时,带有 -1 标记的数据会“覆盖”或“抵消”相应的有效数据(带 1 标记的数据),这就是所谓的 “消除” 操作。
CollapsingMergeTree 表通常包含以下几个部分:
- 数据列:存储实际的数据。
- 标记列(sign column):存储删除标记(通常是 1 或 -1)。
合并操作:
在 CollapsingMergeTree 引擎中,合并操作(类似于其他 MergeTree 引擎)会根据 sign 列的值来合并数据。
- 如果有重复的记录(相同主键),合并操作会根据标记列的值(1 或 -1)来决定是否保留该记录。默认情况下,在合并过程中,sign = -1 的记录会“消除”同一主键下的 sign = 1 的记录。
- 合并操作是异步的,通常发生在后台自动执行,当满足合并条件时才会触发。
使用场景:
CollapsingMergeTree 引擎特别适合以下场景:
- 删除标记的处理:适用于需要在表中标记删除的场景,如日志数据、事件流数据等。
- 反向操作的场景:例如,处理撤销操作(如用户撤销某个操作,或者支付退款)。
- 多版本数据管理:当系统需要管理数据的版本时,可以通过 sign 列来标记旧版本的删除
使用示例:
CREATE TABLE user_actions
(
user_id UInt32,
action_type String,
timestamp DateTime,
sign Int8, -- 标记列,用于标识有效或删除数据
-- 其他数据列
)
ENGINE = CollapsingMergeTree(sign)
ORDER BY (user_id, timestamp);
- sign:标记列,用于标识当前记录的状态,1 表示有效数据,-1 表示删除数据。
插入数据:
-- 插入有效数据
INSERT INTO user_actions (user_id, action_type, timestamp, sign)
VALUES (1, 'login', '2024-11-27 10:00:00', 1);
-- 插入删除数据
INSERT INTO user_actions (user_id, action_type, timestamp, sign)
VALUES (1, 'login', '2024-11-27 10:00:00', -1);
首先插入了一条有效数据(sign = 1),然后插入了一条标记为删除的记录(sign = -1)。这样,CollapsingMergeTree 会将这两条记录进行消除(合并),最终查询时只有有效的记录。
查询数据:
查询时,CollapsingMergeTree 会自动根据 sign 列的值来处理数据,消除标记为删除的数据。只会返回没有被“覆盖”的有效数据。
SELECT user_id, action_type, timestamp
FROM user_actions
WHERE user_id = 1;
-
如果先插入了一条有效数据(sign = 1),再插入一条删除数据(sign = -1),这两条记录会被消除,查询结果只会返回有效的记录。
-
如果先插入了一条删除数据(sign = -1),再插入一条有效数据(sign = 1),这两条记录不会被消除,查询结果会返回两条记录
ClickHouse 会认为这两行数据不需要抵消。因为最后插入的行(deleteFlag 为 1)表示新增,而前面的行(deleteFlag 为 -1)表示删除。由于没有前面的 1 与之抵消,这两行数据不会被合并。
-
如果没有删除记录,查询会返回所有插入的数据
- VersionedCollapsingMergeTree
VersionedCollapsingMergeTree 是一个改进版本的 CollapsingMergeTree 表引擎,专门用于处理有版本控制的去重操作。它的设计目的是能够更精确地处理类似“版本管理”或“历史数据”场景,特别是在处理带有多个版本的数据时,通过版本号来判定哪些记录应该被保留,哪些应该被消除。
与普通的 CollapsingMergeTree 引擎类似,VersionedCollapsingMergeTree 也用于存储带有“标记列”(sign)的表,在处理时通过合并操作去除无效数据。不过,VersionedCollapsingMergeTree 引入了 版本号(通常是通过 version 列来标识)来进行精细的控制。具体来说,它通过版本号来决定数据的更新、删除和保留。
- CollapsingMergeTree 主要依靠 sign 列来标记记录是否被删除,删除操作是基于 sign 列的值来执行(sign = 1 表示有效,sign = -1 表示删除)。
- VersionedCollapsingMergeTree 除了 sign 列,还加入了 version 列,允许为每个主键创建多个版本。版本号的引入,使得它可以在某个主键的不同版本之间,进行更加复杂的去重和更新操作。
VersionedCollapsingMergeTree 表通常包含以下几个部分:
- 数据列:存储实际的数据。
- 标记列(sign):存储删除标记(通常是 1 或 -1)。
- 版本号列(version):用于区分数据的不同版本。每当记录被修改或更新时,都会增加版本号,而不覆盖原有的数据。
合并操作:
VersionedCollapsingMergeTree 的合并操作类似于普通的 CollapsingMergeTree,但是增加了对版本号的考虑。合并过程中的主要原则如下:
-
根据主键和版本号合并数据:
每条数据记录有一个 id 和一个版本号(version)。合并时,ClickHouse 会根据主键 id 和版本号来判断是否有相同的数据。当合并数据时,系统会优先保留版本号较大的数据。如果多个版本的记录都有 sign = 1,则版本号较大的记录会被保留,其他较旧的版本会被丢弃。 -
消除标记数据:
当某条记录被标记为删除(sign = -1)时,合并操作会消除相应的记录。例如,当一条数据(sign = 1)的记录被新的版本(sign = -1)所删除,合并时会将其删除。如果存在多个相同版本的记录,系统会依据版本号和时间戳来决定哪些数据应该被保留,哪些应该被删除。 -
合并后的版本数据:
合并后的数据保留版本号最大的一条有效数据,而较旧版本的数据会被丢弃。这使得 VersionedCollapsingMergeTree 可以在处理复杂数据更新时,保留最新版本的数据,并且在不丢失历史版本的情况下,处理数据的更新、删除等操作。 -
合并操作是异步的,通常发生在后台自动执行,当满足合并条件时才会触发。
使用示例:
CREATE TABLE versioned_table
(
id UInt32, -- 主键字段
version UInt32, -- 版本号字段
data String, -- 数据字段
sign Int8, -- 删除标记字段 (sign = 1 表示有效,sign = -1 表示删除)
event_time DateTime -- 时间戳字段
)
ENGINE = VersionedCollapsingMergeTree(sign,version)
ORDER BY (id);
- GraphiteMergeTree
Replicated*MergeTree
ClickHouse中的所有MergeTree家族引擎前面加上Replicated就成了支持副本的合并树引擎。
Replicated系列引擎借助ZooKeeper实现数据的同步,创建Replicated复制表时通过注册到ZooKeeper上的信息实现同一个分片的所有副本数据进行同步。
参考文章:
https://clickhouse.com/docs/zh/operations/settings/merge-tree-settings