读懂 ClickHouse 分区目录

24 阅读4分钟

在使用 ClickHouse 的过程中,很多人都会在数据目录下看到类似下面这样的目录名:

20251231_1_3_1
20260101_4_5_1
20260102_6_8_2

这些目录名看起来“像是有规律,但又说不清规律”,尤其是:

  • MinBlockNum / MaxBlockNum 是怎么来的?
  • Level 到底表示什么?
  • 为什么同一天会不断变化?

本文将通过完整写入 + 合并过程示例,把 MergeTree 的分区目录命名规则彻底讲清楚。


一、表结构与前提条件

假设有如下表结构:

CREATE TABLE hits_v1
(
    ts Date,
    uid UInt64,
    url String
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(ts)
ORDER BY uid;

关键点:

  • 每天一个分区
    分区 ID(PartitionId) = YYYYMMDD(ts)
  • 每次 INSERT 写入都会生成新的 数据块(part)
  • MergeTree 会在后台 自动 merge(合并)数据块

二、先明确几个核心概念(非常重要)

1️⃣ Partition(分区)

  • PARTITION BY 决定
  • 是数据管理、删除、合并的大粒度单位
  • 不同分区之间永不合并

例如:

20251231  ← 一个分区
20260101  ← 另一个分区

2️⃣ BlockNum(块编号)

  • 全表级自增编号
  • 每生成一个新的 data part,就分配一个新的编号
  • 不会因分区而重置

👉 这是很多人第一次容易误解的点:
BlockNum 不是“分区内编号”,而是全局编号


3️⃣ Level(合并层级)

  • 表示这个 part 经历过多少次 merge
  • 每 merge 一次,Level + 1
  • Level 越大,part 越“成熟”、体积越大、文件越少

三、分区目录命名格式

MergeTree 分区目录的命名规则为:

PartitionId_MinBlockNum_MaxBlockNum_Level

含义是:

某个分区内,由 MinBlockNum 到 MaxBlockNum 这些原始数据块合并而成,当前合并层级为 Level 的数据 part


四、完整写入 & 合并过程示例

📅 第 1 天:2025-12-31

写入第 1 个数据块

20251231_1_1_0

解释:

  • PartitionId = 20251231
  • MinBlockNum = 1
  • MaxBlockNum = 1
  • Level = 0(原始数据块,未合并)

写入第 2 个数据块

20251231_1_2_0

说明:

  • 新写入块编号为 2
  • 分区内现在有两个原始块
  • 仍未发生合并

写入第 3 个数据块

20251231_1_3_0

第一次自动 merge

系统将 块 1 + 块 2 合并为一个更大的 part:

20251231_1_3_1

注意这里的含义:

  • MinBlockNum = 1
  • MaxBlockNum = 3
    👉 表示这个 part 覆盖了编号 1~3 的数据来源
  • Level = 1
    👉 表示经历过一次 merge

⚠️ 这里并不是“块 3 也被合并了”
而是:新 part 的“血缘范围”是 1~3


📅 第 2 天:2026-01-01

写入第 4 个数据块(新分区)

20260101_4_4_0

说明:

  • 新分区
  • BlockNum 继续全局递增
  • Level = 0

写入第 5 个数据块

20260101_4_5_0

第一次 merge

20260101_4_5_1

📅 第 3 天:2026-01-02

连续写入三个块

20260102_6_6_0
20260102_6_7_0
20260102_6_8_0

第一次 merge

20260102_6_8_1

第二次 merge(更大的 part)

20260102_6_8_2

此时:

  • Level = 2
  • 数据已经高度合并
  • 查询时只需读取极少量文件

五、命名规则背后的设计意义

为什么要这样命名?

1️⃣ 快速判断数据血缘

  • MinBlockNum / MaxBlockNum 明确这个 part 覆盖了哪些原始数据

2️⃣ 支持高效 merge 调度

  • 系统可以根据 Level 判断哪些 part 适合再合并

3️⃣ 查询更快

  • Level 越高 → 文件越少 → IO 越少

六、规律总结(速记版)

字段含义
PartitionId分区键值(如日期)
MinBlockNum覆盖的最小原始块编号
MaxBlockNum覆盖的最大原始块编号
Level合并次数

整体规律:

  • 每天一个分区
  • 每次写入 → BlockNum 全局递增
  • 每次 merge → Level +1
  • 分区目录名 = “数据来源范围 + 合并深度”

七、一句话总结

MergeTree 的分区目录名,本质上是 ClickHouse 用来描述“这个数据块从哪些原始数据合并而来,以及它已经被合并到什么程度”的一种元信息编码。

补充: ❌ 查询时不是“优先选择 Level 最大的 part”
查询时是:对分区内“当前可见的所有 active part”,全部参与查询