自定义分区键
在大多数情况下,您不需要分区键,而在其他大多数情况下,您不需要比按月更细粒度的分区键。
您永远不应使用过于细粒度的分区。不要按客户端标识符或名称对数据进行分区。相反,请将客户端标识符或名称设为 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
- 表可以拥有的最大分区数的硬性限制