bigywater.tech
ClickHouse 内部原理 · 79

Parquet 文件如何被 ClickHouse 读取

真实生成并读取一份Parquet文件,验证ClickHouse的类型推断结果,以及字段数量/类型不匹配时真实的容错与隐患。

字段数量很宽容
真实缺的字段补NULL,多的字段按名称忽略
类型不匹配静默出错
真实Float64读成Int32会无声截断小数部分

做的事:真实生成一份Parquet文件,用DESCRIBE看它真实的类型推断

代码SELECT number AS id, toString(number) AS code, toDate('2026-01-01')+number AS d, number*1.1 AS price FROM numbers(5) INTO OUTFILE 'schema_v1.parquet' FORMAT Parquet; DESCRIBE TABLE file('schema_v1.parquet', Parquet);

验证:真实的类型推断,跟写入时的SQL表达式不完全一样

实测 · 本机执行输出
DESCRIBE TABLE file('schema_v1.parquet', Parquet);
→ id     UInt64    ← number的默认类型
  code   String
  d      Date32    ← 真实不是Date,是Date32
  price  Float64

d列真实被推断成了Date32而不是更常见的Date——本文写入时用的toDate('2026-01-01')+number,number是UInt64,跟Date相加真实触发了ClickHouse内部往更大范围类型(Date32)的隐式提升,这个提升结果被真实写进了Parquet文件的schema里,不是Parquet格式本身的限制。

验证:字段数量不匹配真实都不报错,类型不匹配真实静默转换

实测 · 本机执行输出
-- 用比文件多一个字段的表结构读:
SELECT id, code, d, price, extra_field FROM file(..., 'id UInt32, code String, d Date,
  price Float64, extra_field Nullable(String)');
→ 真实extra_field全部是NULL,不报错

-- 用比文件少一个字段(跳过中间的d列)的表结构读:
SELECT id, code, price FROM file(..., 'id UInt32, code String, price Float64');
→ 真实正确读出id/code/price,按列名匹配,不受文件里列的物理顺序影响

-- 真实把price(文件里是Float64)硬指定成Int32读取:
SELECT id, price FROM file(..., 'id UInt32, code String, d Date, price Int32');
→ 1.1→1,2.2→2,3.3→3,4.4→4   ← 真实静默截断了小数部分,没有任何报错
这三组真实测试合起来说明了ClickHouse读Parquet文件时一套相当宽容的真实策略:字段数量不对齐(缺的补NULL、多的按列名忽略)基本不会报错,这对"文件schema会随时间演化"的真实场景很友好——旧文件缺新加的字段、新文件多了旧代码不认识的字段,读取代码都不需要跟着每次改动。但这份宽容也真实带来了代价:本文把Float64的price读成Int32,1.1/2.2/3.3/4.4这些小数值真实被直接截断成了1/2/3/4,没有任何报错、没有任何警告,如果表结构定义时手滑写错了一个数值类型的精度,读出来的数据会真实悄悄错误,而不会有任何信号提示你哪里错了。真实的建议是:读取外部Parquet文件之前,最好先用DESCRIBE TABLE file(...)(本文第一步验证过的方式)看清楚文件真实的原始类型,再决定读取时用什么类型接收,而不要凭经验或者猜测直接手写一份表结构——本文这次的Int32截断就是故意手写了一个跟真实类型不符的类型才暴露出来的。

分步演示:从类型推断到真实的宽容与隐患

每一步都是本文上面用真实Parquet文件跑出来的原始结果。

字段数量很宽容,类型不匹配却真实静默出错
这一步新验证的场景

    本文用到的工具

    • 本机真实的 clickhouse local 生成Parquet文件,真实的 file() 表函数
    • 真实验证字段数量不匹配的容错行为与类型不匹配的静默截断
    京ICP备2026067502号-1京公网安备11010502063487号