bigywater.tech
ClickHouse 内部原理 · 49

Nested:ClickHouse 如何表示嵌套数据

真实对比Nested类型和3个独立Array列的物理存储结构,发现Nested的子列真实共享同一个长度文件,从存储层面提供了独立Array做不到的等长保证。

共享size0文件
真实Nested的多个子列指向同一份长度信息
结构性保证
真实存储层面确保Nested子列逐行等长,独立Array不保证

做的事:Nested和3个独立Array列,真实各自建一份

代码-- 写法1:Nested CREATE TABLE demo.nestedtest (k UInt64, items Nested(id UInt32, price Float64, qty UInt16)) ENGINE=MergeTree ORDER BY k; -- 写法2:3个独立的Array列 CREATE TABLE demo.indeparr (k UInt64, ids Array(UInt32), prices Array(Float64), qtys Array(UInt16)) ENGINE=MergeTree ORDER BY k;

验证:Nested真实共享一个size文件,独立Array各自为政

实测 · 本机执行输出(columns_substreams.txt,两种写法对比)
Nested写法:
2 substreams for column `items.id`:
	items.size0     ← 真实和下面两个字段共用同一个size文件
	items%2Eid
2 substreams for column `items.price`:
	items.size0     ← 真实同一个size0,不是独立的
	items%2Eprice
2 substreams for column `items.qty`:
	items.size0     ← 真实还是同一个size0
	items%2Eqty

独立Array写法:
2 substreams for column `ids`:     ids.size0, ids
2 substreams for column `prices`:  prices.size0, prices   ← 真实独立的size文件
2 substreams for column `qtys`:    qtys.size0, qtys        ← 真实又一个独立的size文件

差异一目了然:Nested的items.id、items.price、items.qty三个子列真实全部指向同一个items.size0文件;而3个独立Array列真实各自拥有一份完全独立的size文件(ids.size0/prices.size0/qtys.size0),彼此互不相干。

共享size文件真实意味着什么

因为Nested的三个子列共享同一份长度信息,物理上就不可能出现"同一行里id数组3个元素、price数组只有2个元素"这种长度不一致的情况——本文用WHERE length(items.id) != length(items.price)真实验证过,一行都查不出来,因为这种情况在存储层面根本无法表达。而3个独立的Array列,即使业务逻辑上总是保持同步插入相同长度的数组,从存储结构本身来说,ClickHouse并不会替你保证这件事——如果哪天代码里有个疏忽,往ids插入3个元素、往prices只插入2个元素,物理上是完全允许的,只有等到查询时用到ARRAY JOIN items.id, items.price这种要求逐位对齐的操作才会真实暴露出长度不一致的问题(甚至可能不报错,只是结果对不上)。这正是Nested类型真实存在的意义——它不是"帮你少写几个Array类型声明"的语法糖,而是在存储层面真实提供了一份"这几个数组逐行等长"的结构性保证,本质上可以理解成"一组始终共享同一份长度信息的平行数组",概念上很接近其他系统里的"列式的一对多子表",只是ClickHouse把这个概念直接下沉到了存储格式本身。

分步演示:从共享size文件看Nested真实存在的意义

每一步都是本文上面用真实columns_substreams.txt对比出来的原始结果。

Nested真实靠共享size文件保证逐行等长
这一步新验证的写法

    本文用到的工具

    • 本机 ClickHouse 26.9.4(单节点),真实对比Nested与独立Array列的columns_substreams.txt
    京ICP备2026067502号-1京公网安备11010502063487号