ClickHouse 中的表分区是什么?
分区会将 MergeTree 引擎家族中表的数据分区片段组织成有序的逻辑单元。这种数据组织方式在概念上清晰明确,并可根据时间范围、类别或其他关键属性等特定标准进行划分。这些逻辑单元使数据更易于管理、查询和优化。
PARTITION BY
PARTITION BY toStartOfMonth(date) 子句来完善 什么是表 parts示例表,该子句会按房产销售月份来组织表的数据分区片段:
磁盘上的结构
ClickHouse server 首先按照分区键值
toStartOfMonth(date),将上图中示意的示例插入里的 4 行拆分开来。
然后,对于识别出的每个分区,这些行会像通常那样经过若干连续步骤进行处理 (① 排序,② 拆分为列,③ 压缩,④ 写入磁盘) 。
请注意,启用分区后,ClickHouse 会自动为每个数据分区片段创建 MinMax 索引。它们本质上就是针对分区键表达式中用到的每个表列生成的文件,其中包含该列在该数据分区片段中的最小值和最大值。
分区内合并
如上图所示,属于不同分区的 parts 永远不会被合并。如果选择了高基数的分区键,那么分散在成千上万个分区中的 parts 将永远不会成为合并候选项——这会超出预先配置的限制,并导致令人头疼的
Too many parts 错误。解决这个问题其实很简单:选择合理的分区键,并将基数控制在 1000 到 10000 以内。
监控分区
_partition_value 来查询示例表中所有现有唯一分区的列表:
此外,ClickHouse 会在 system.parts 系统表中跟踪所有表的 parts 和分区。以下查询将返回上述示例表中所有分区的列表,以及每个分区当前活跃 parts 的数量和这些 parts 中的行数总和:
表分区有什么作用?
数据管理
toStartOfMonth(date) 进行分区,因此,满足生存时间 (TTL) 条件的整个分区 (即一组表 parts) 都会被直接删除,从而使清理操作更高效,无需重写 parts。
同样,无需删除旧数据,也可以自动高效地将其迁移到成本更低的存储层级:
查询优化
date) 以及表主键中使用的列 (town) 进行过滤 (且 date 不属于主键的一部分) ,来计算 2020 年 12 月伦敦所有已售房产中的最高价格。
ClickHouse 通过依次应用一系列剪枝技术来处理该查询,从而避免评估无关数据:
① 分区剪枝:MinMax 索引 用于忽略在逻辑上不可能匹配查询过滤条件的整个分区 (即一组 parts) ;这些过滤条件作用于表分区键中使用的列。 ② 粒度剪枝:对于步骤 ① 之后剩余的数据 parts,会使用其主索引忽略所有在逻辑上不可能匹配查询过滤条件的粒度 (由多行组成的块) ;这些过滤条件作用于表主键中使用的列。 我们可以通过查看上面示例查询的物理执行计划,来观察这些数据剪枝步骤,具体可借助 EXPLAIN 子句:
date 字段的 MinMax 索引,在 436 个现有活动数据 parts 中的 1 个里,识别出 3257 个现有粒度 (由多行组成的块) 中的 11 个,这些粒度包含与查询 date 过滤器匹配的行。
② 粒度剪枝:上面 EXPLAIN 输出的第 19 到 24 行表明,ClickHouse 随后使用步骤 ① 中识别出的数据 parts 的主索引 (基于 town 字段创建) ,将粒度数量 (其中的行也可能匹配查询 town 过滤器) 从 11 个进一步减少到 1 个。这一点也反映在我们上文打印的该查询的 ClickHouse-client 输出中:
分区主要是一项数据管理功能
uk_price_paid_simple_partitioned 有 600 多个分区,因此有 600 个分区、306 个活跃数据 parts。而对于未分区的表 uk_price_paid_simple,所有初始数据 parts 都可以通过后台合并,最终合并为一个活跃分片。
当我们对上文中的示例查询 (在分区表上执行,但不带分区过滤器) 使用 EXPLAIN 子句来查看其物理查询执行计划时,可以从下面输出结果的第 19 和第 20 行看到,ClickHouse 在现有 3257 个粒度 (行块) 中识别出其中 671 个,它们分布在现有 436 个活跃数据 parts 中的 431 个上,并且可能包含与查询过滤条件匹配的行,因此查询引擎将扫描并处理这些数据: