自定义分区键
在大多数情况下,您不需要分区键,而在大多数其他情况下,您不需要比按月更细粒度的分区键。
您永远不应该使用过于细粒度的分区。不要按客户端标识符或名称对数据进行分区。相反,请将客户端标识符或名称作为 ORDER BY 表达式中的第一列。
分区适用于 MergeTree 系列表,包括 复制表 和 物化视图。
分区是根据指定条件对表中记录进行逻辑组合。您可以根据任意条件设置分区,例如按月、按日或按事件类型。每个分区都单独存储,以简化对这些数据的操作。访问数据时,ClickHouse 会使用尽可能小的分区子集。分区可以提高包含分区键的查询的性能,因为 ClickHouse 会在选择分区内的部分和数据块之前过滤该分区。
分区在创建 表 时,在 PARTITION BY expr
子句中指定。分区键可以是表列中的任何表达式。例如,要指定按月分区,请使用表达式 toYYYYMM(date_column)
CREATE TABLE visits
(
VisitDate Date,
Hour UInt8,
ClientID UUID
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(VisitDate)
ORDER BY Hour;
分区键也可以是表达式的元组(类似于 主键)。例如
ENGINE = ReplicatedCollapsingMergeTree('/clickhouse/tables/name', 'replica1', Sign)
PARTITION BY (toMonday(StartDate), EventType)
ORDER BY (CounterID, StartDate, intHash32(UserID));
在此示例中,我们按本周发生的事件类型设置分区。
默认情况下,不支持浮点分区键。要使用它,请启用设置 allow_floating_point_partition_key。
将新数据插入表时,这些数据将作为按主键排序的单独部分(块)存储。插入后 10-15 分钟,同一分区的各部分将合并为整个部分。
合并仅适用于分区表达式具有相同值的数据库部分。这意味着您不应该创建过于细粒度的分区(超过大约一千个分区)。否则,由于文件系统中文件数量过多和打开的文件描述符过多,SELECT
查询的性能会下降。
使用 system.parts 表查看表部分和分区。例如,假设我们有一个按月分区的 visits
表。让我们对 system.parts
表执行 SELECT
查询
SELECT
partition,
name,
active
FROM system.parts
WHERE table = 'visits'
┌─partition─┬─name──────────────┬─active─┐
│ 201901 │ 201901_1_3_1 │ 0 │
│ 201901 │ 201901_1_9_2_11 │ 1 │
│ 201901 │ 201901_8_8_0 │ 0 │
│ 201901 │ 201901_9_9_0 │ 0 │
│ 201902 │ 201902_4_6_1_11 │ 1 │
│ 201902 │ 201902_10_10_0_11 │ 1 │
│ 201902 │ 201902_11_11_0_11 │ 1 │
└───────────┴───────────────────┴────────┘
partition
列包含分区的名称。在此示例中,有两个分区:201901
和 201902
。您可以使用此列值在 ALTER ... PARTITION 查询中指定分区名称。
name
列包含分区数据部分的名称。您可以使用此列在 ALTER ATTACH PART 查询中指定部分的名称。
让我们分解部分的名称:201901_1_9_2_11
201901
是分区名称。1
是数据块的最小编号。9
是数据块的最大编号。2
是块级别(它形成的合并树的深度)。11
是变异版本(如果部分发生变异)。
旧类型表的各部分具有以下名称:20190117_20190123_2_2_0
(最小日期 - 最大日期 - 最小块编号 - 最大块编号 - 级别)。
active
列显示部分的状态。1
表示活动;0
表示非活动。例如,非活动部分是在合并到较大部分后剩余的源部分。损坏的数据部分也显示为非活动状态。
如您在示例中看到的,同一分区有几个分离的部分(例如,201901_1_3_1
和 201901_1_9_2
)。这意味着这些部分尚未合并。ClickHouse 定期合并插入的数据部分,大约在插入后 15 分钟。此外,您可以使用 OPTIMIZE 查询执行非计划合并。示例
OPTIMIZE TABLE visits PARTITION 201902;
┌─partition─┬─name─────────────┬─active─┐
│ 201901 │ 201901_1_3_1 │ 0 │
│ 201901 │ 201901_1_9_2_11 │ 1 │
│ 201901 │ 201901_8_8_0 │ 0 │
│ 201901 │ 201901_9_9_0 │ 0 │
│ 201902 │ 201902_4_6_1 │ 0 │
│ 201902 │ 201902_4_11_2_11 │ 1 │
│ 201902 │ 201902_10_10_0 │ 0 │
│ 201902 │ 201902_11_11_0 │ 0 │
└───────────┴──────────────────┴────────┘
非活动部分将在合并后大约 10 分钟内删除。
查看部分和分区集的另一种方法是进入表的目录:/var/lib/clickhouse/data/<database>/<table>/
。例如
/var/lib/clickhouse/data/default/visits$ ls -l
total 40
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 1 16:48 201901_1_3_1
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 5 16:17 201901_1_9_2_11
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 5 15:52 201901_8_8_0
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 5 15:52 201901_9_9_0
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 5 16:17 201902_10_10_0
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 5 16:17 201902_11_11_0
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 5 16:19 201902_4_11_2_11
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 5 12:09 201902_4_6_1
drwxr-xr-x 2 clickhouse clickhouse 4096 Feb 1 16:48 detached
文件夹“201901_1_1_0”、“201901_1_7_1”等是各部分的目录。每个部分都与相应的分区相关联,并且仅包含特定月份的数据(此示例中的表按月进行分区)。
detached
目录包含使用 DETACH 查询从表中分离的部分。损坏的部分也会移动到此目录,而不是被删除。服务器不使用 detached
目录中的部分。您可以随时添加、删除或修改此目录中的数据 - 服务器在您运行 ATTACH 查询之前不会知道这一点。
请注意,在操作系统服务器上,您不能手动更改文件系统上的部分集或其数据,因为服务器不会知道这一点。对于非复制表,您可以在服务器停止时执行此操作,但不建议这样做。对于复制表,无论如何都不能更改部分集。
ClickHouse 允许您对分区执行操作:删除它们、从一个表复制到另一个表或创建备份。请参阅 分区和部分操作 部分中所有操作的列表。
使用分区键优化 Group By
对于表的分区键和查询的 group by 键的某些组合,可能可以独立地对每个分区执行聚合。然后,我们不必在最后合并来自所有执行线程的部分聚合数据,因为我们提供了保证,即每个 group by 键值都不能出现在两个不同线程的工作集中。
典型的例子是
CREATE TABLE session_log
(
UserID UInt64,
SessionID UUID
)
ENGINE = MergeTree
PARTITION BY sipHash64(UserID) % 16
ORDER BY tuple();
SELECT
UserID,
COUNT()
FROM session_log
GROUP BY UserID;
此类查询的性能在很大程度上取决于表布局。因此,默认情况下未启用优化。
良好性能的关键因素
- 查询涉及的分区数量应足够大(大于
max_threads / 2
),否则查询将无法充分利用机器 - 分区不应该太小,这样批处理就不会退化为逐行处理
- 分区的大小应大致相同,这样所有线程将执行大致相同的工作量
建议对 partition by
子句中的列应用一些哈希函数,以便将数据均匀地分布到各分区之间。
相关的设置是
allow_aggregate_partitions_independently
- 控制是否启用优化的使用force_aggregate_partitions_independently
- 在从正确性角度看适用时强制使用它,但会被估计其权宜之计的内部逻辑禁用max_number_of_partitions_for_independent_aggregation
- 表可以具有的最大分区数量的硬限制