跳转到主要内容
ClickHouse 查询性能的核心原因之一在于其高效的数据压缩。磁盘上的数据越少,I/O 开销就越低,查询和写入也就越快。ClickHouse 的列式架构会自然地将相似数据排列在一起,使压缩算法和编解码器能够大幅减少数据体积。要最大化这些压缩收益,谨慎选择合适的数据类型至关重要。 ClickHouse 中的压缩效率主要取决于三个因素:排序键、数据类型和编解码器,它们都通过表 schema 定义。选择合适的数据类型,可以立即提升存储效率和查询性能。 一些简单明了的指导原则可以显著优化 schema:
  • 使用严格类型: 始终为列选择正确的数据类型。数值和日期字段应使用相应的数值和日期类型,而不是通用的 String 类型。这可以确保过滤和聚合的语义正确。
  • 避免 Nullable 列: Nullable 列需要维护额外的列来跟踪 NULL 值,因此会引入额外开销。只有在明确需要区分空值和 NULL 状态时才应使用 Nullable。否则,默认值或等效的零值通常就足够了。有关为什么除非必要否则应避免使用此类型的更多信息,请参阅 避免 Nullable 列
  • 尽量降低数值精度: 选择位宽尽可能小、但仍能容纳预期数据范围的数值类型。例如,如果不需要负值,且范围落在 0–65535 之间,则应优先使用 UInt16 而不是 Int32
  • 优化日期和时间精度: 选择能够满足查询需求的、粒度尽可能粗的日期或日期时间类型。对于仅包含日期的字段,使用 Date 或 Date32;除非确实需要毫秒级或更高精度,否则优先使用 DateTime 而不是 DateTime64。
  • 利用 LowCardinality 和专用类型: 对于唯一值少于约 10,000 个的列,使用 LowCardinality 类型可以通过字典编码显著减少存储占用。同样,只有当列值严格为定长字符串时才应使用 FixedString (例如国家或货币代码) ;而对于可能值集合有限的列,则优先使用枚举类型,以实现更高效的存储和内置的数据验证。
  • 使用枚举进行数据验证: 枚举类型可用于高效编码枚举值。根据需要存储的唯一值数量,枚举可以是 8 位或 16 位。如果你需要写入时验证 (未声明的值会被拒绝) ,或者希望执行能够利用枚举值自然顺序的查询,可以考虑使用它。例如,某个反馈列可包含用户响应 Enum(’:(’ = 1, ’:|’ = 2, ’:)’ = 3)。

示例

ClickHouse 提供了内置工具,可简化类型优化。例如,schema 推断可以自动识别初始类型。以公开提供的 Parquet 格式 Stack Overflow 数据集为例,通过 DESCRIBE 命令执行简单的 schema 推断,即可获得一个初始的、尚未优化的 schema。
默认情况下,ClickHouse 会将这些映射为对应的 Nullable 类型。这样做更合适,因为 schema 仅基于部分行的样本。
请注意,下面我们使用 glob pattern *.parquet 读取 stackoverflow/parquet/posts 文件夹中的所有文件。
将前面的一些简单规则应用到 posts 表后,我们可以为每一列确定最优类型:
提示确定列的合适类型,需要了解其数值范围和唯一值的数量。要找出所有列的取值范围以及不同值的数量,可以使用这个简单的查询:SELECT * APPLY min, * APPLY max, * APPLY uniq FROM table FORMAT Vertical。我们建议仅对较小的数据子集执行此操作,因为这样做的开销可能较大。
这样会得到如下按类型优化后的 schema:

避免使用 Nullable 列

Nullable (例如 Nullable(String)) 会额外创建一个 UInt8 类型的独立列。每次用户操作 Nullable 列时,都必须处理这个附加列。这会占用额外的存储空间,并且几乎总会对性能产生负面影响。 为了避免使用 Nullable 列,可以考虑为该列设置默认值。例如,不要这样写:
使用
请根据你的使用场景进行判断;默认值可能并不适用。
最后修改于 2026年7月3日