bigywater.tech
ClickHouse 内部原理 · 10

Data Part:一次 INSERT 到底生成了什么文件

执行一次INSERT,直接ls磁盘上的part目录,逐个文件看清楚一个part到底由什么组成,再对比Compact和Wide两种格式的文件差异。

13个文件
1次INSERT生成的part文件数
自描述
columns.txt让每个part不依赖外部表定义
Compact vs Wide
同样数据生成完全不同的文件布局

做的事:插入3行数据,直接看磁盘上多了什么

建一张按月分区的表,插入3行,然后ls生成的part目录。

建表插入,然后查part路径CREATE TABLE demo.partdemo (d Date, k UInt32, name String) ENGINE=MergeTree PARTITION BY toYYYYMM(d) ORDER BY k INSERT INTO demo.partdemo VALUES ('2026-05-01',1,'alice'),('2026-05-02',2,'bob'),('2026-05-03',3,'carol') SELECT path FROM system.parts WHERE table='partdemo' AND active → .../store/f41.../202605_1_1_0/ ls 这个目录: checksums.txt columns.txt columns_substreams.txt count.txt data.bin data.cmrk4 default_compression_codec.txt metadata_version.txt minmax_e039...idx partition.dat primary.cidx serialization.json statistics.packed

一次 INSERT 在磁盘上新建了这一整组文件,这就是一个 part。part 目录名 202605_1_1_0 本身就有含义:202605 是分区,1_1 是这个part包含的最小-最大数据块号,0 是level(还没被合并过)。

验证:几个关键文件里到底装了什么

直接cat几个文本格式的文件,看清楚它们存的是什么。

内容$ cat columns.txt columns format version: 1 3 columns: `d` Date `k` UInt32 `name` String $ cat count.txt 3 $ head checksums.txt checksums format version: 4 (后面是每个文件名+对应的校验和,二进制格式)

columns.txt 记录了这个part里到底有哪些列、什么类型——这意味着每个part都能自描述,不依赖外部的表结构定义就能被单独读懂。count.txt 就是一个数字,这个part有多少行,查 count() 这种聚合有时可以直接读这一个文件而不用扫描数据。checksums.txt 则是这个part里每一个数据文件的校验和,ClickHouse 靠它在启动时校验数据有没有损坏。

验证:Compact part vs Wide part,文件长得完全不一样

同样3行3列的数据,只改一个建表参数,生成的文件数量和命名方式完全不同。

对比-- 默认(小part自动用Compact格式) SELECT part_type FROM system.parts WHERE table='partdemo' → Compact 文件:只有1个 data.bin(所有列的数据混在一个文件里) -- 加 SETTINGS min_bytes_for_wide_part=0,强制用Wide格式 SELECT part_type FROM system.parts WHERE table='partwide' → Wide 文件: 258dee3....bin + 258dee3....cmrk2 9a482fe....bin + 9a482fe....cmrk2 a39e781....bin + a39e781....cmrk2 e039b45....bin + e039b45....cmrk2

Compact 格式下,不管这个part有几列,只有1个 data.bin 文件,所有列的数据顺序堆在一起——这是专门为"数据量很小的part"设计的,减少小文件数量。Wide 格式则为每一列单独生成一对文件(.bin 存数据,.cmrk2 存这一列的标记/索引),文件名不是列名本身,而是列名的哈希值——这样即使列名包含特殊字符或者非常长,也不会影响文件系统上的合法性。

ClickHouse 会根据这个part的大小自动在两种格式间选择(由 min_bytes_for_wide_part 等阈值控制):part小就用Compact省文件数量,part大就用Wide让每一列可以独立读取——这跟本系列第2篇里"列存只读需要的列"是同一个道理,只是Compact格式为了省小文件的开销,牺牲了这种按列独立读取的能力。

分步演示:一次INSERT生成的part,逐个文件拆开看

每一步都是本文上面ls磁盘目录、cat文件内容得到的原始结果。

一个part由哪些文件组成,Compact和Wide差在哪
这一步新验证的内容

    本文用到的工具

    • system.parts:定位part在磁盘上的路径与part_type
    • ls / cat:直接查看part目录里的文件
    • SETTINGS min_bytes_for_wide_part=0:强制生成Wide格式part做对比
    京ICP备2026067502号-1京公网安备11010502063487号