跳到主要内容
跳到主要内容

选择数据类型

ClickHouse 查询性能的一个核心原因是其高效的数据压缩。磁盘上的数据更少通过最小化 I/O 开销来实现更快的查询和插入。 ClickHouse 的列式架构自然将相似数据相邻排列,使压缩算法和编解码器能够显著减少数据大小。为了最大限度地利用这些压缩效益,务必仔细选择合适的数据类型。

在 ClickHouse 中,压缩效率主要取决于三个因素:排序键、数据类型和编解码器,这些都通过表架构定义。选择最佳数据类型可以在存储和查询性能上立即获得改善。

一些简单的指导方针可以显著增强架构:

  • 使用严格类型: 始终为列选择正确的数据类型。数字和日期字段应使用适当的数字和日期类型,而不是通用的字符串类型。这可以确保过滤和聚合时逻辑的正确性。

  • 避免 Nullable 列: Nullable 列通过保持单独的列来跟踪空值,从而引入额外的开销。只有在明确需要区分空状态和 null 状态时才使用 Nullable。否则,默认值或零等效值通常就足够了。有关为什么应避免此类型的更多信息,请参见 Avoid Nullable Columns

  • 最小化数字精度: 选择位宽最小的数字类型,以便仍然能够容纳预期的数据范围。例如,如果不需要负值并且范围在 0–65535 之间,则选择 UInt16 over Int32

  • 优化日期和时间精度: 选择满足查询要求的最粗粒度的日期或日期时间类型。日期字段只使用 Date 或 Date32,对于 DateTime 字段,应优先选择 DateTime 而不是 DateTime64,除非需要毫秒级或更精细的精度。

  • 利用 LowCardinality 和专用类型: 对于唯一值少于大约 10,000 的列,使用 LowCardinality 类型可以通过字典编码显著减少存储。同样,仅当列值是严格固定长度的字符串(例如国家或货币代码)时才使用 FixedString,并且对具有有限可能值集的列,优先使用 Enum 类型,以实现有效存储和内置数据验证。

  • 数据验证的枚举类型: Enum 类型可以有效编码枚举类型。根据需要存储的唯一值数量,枚举可以是 8 位或 16 位。如果在插入时需要关联验证(未声明的值将被拒绝)或希望执行利用 Enum 值中自然顺序的查询(例如想象一个包含用户响应的反馈列 Enum(':(' = 1, ':|' = 2, ':)' = 3),考虑使用此功能。

示例

ClickHouse 提供内置工具来简化类型优化。例如,架构推断可以自动识别初始类型。考虑 Stack Overflow 数据集,其以 Parquet 格式公开提供。通过 DESCRIBE 命令运行简单的架构推断可以得到初步的非优化架构。

备注

默认情况下,ClickHouse 将这些映射为相应的 Nullable 类型。这是偏好的,因为架构仅基于行的样本。

备注

请注意,我们下面使用 glob 模式 *.parquet 来读取 stackoverflow/parquet/posts 文件夹中的所有文件。

通过将我们早期的简单规则应用于我们的 posts 表,我们可以为每一列确定最佳类型:

是否数字最小值,最大值唯一值Nulls评论优化类型
PostTypeId1, 88Enum('Question' = 1, 'Answer' = 2, 'Wiki' = 3, 'TagWikiExcerpt' = 4, 'TagWiki' = 5, 'ModeratorNomination' = 6, 'WikiPlaceholder' = 7, 'PrivilegeWiki' = 8)
AcceptedAnswerId0, 7828517012282094区分 Null 与 0 值UInt32
CreationDate2008-07-31 21:42:52.667000000, 2024-03-31 23:59:17.697000000-不需要毫秒粒度,使用 DateTimeDateTime
Score-217, 349703236Int32
ViewCount2, 13962748170867UInt32
Body--String
OwnerUserId-1, 40569156256237Int32
OwnerDisplayName-181251考虑将 Null 视为空字符串String
LastEditorUserId-1, 999999311046940 作为未使用的值,可用于 NullsInt32
LastEditorDisplayName-70952考虑将 Null 视为一个空字符串。经过测试,LowCardinality 没有收益String
LastEditDate2008-08-01 13:24:35.051000000, 2024-04-06 21:01:22.697000000-不需要毫秒粒度,使用 DateTimeDateTime
LastActivityDate2008-08-01 12:19:17.417000000, 2024-04-06 21:01:22.697000000-不需要毫秒粒度,使用 DateTimeDateTime
Title--考虑将 Null 视为一个空字符串String
Tags--考虑将 Null 视为一个空字符串String
AnswerCount0, 518216考虑将 Null 与 0 视为相同UInt16
CommentCount0, 135100考虑将 Null 与 0 视为相同UInt8
FavoriteCount0, 2256考虑将 Null 与 0 视为相同UInt8
ContentLicense-3LowCardinality 性能优于 FixedStringLowCardinality(String)
ParentId-20696028考虑将 Null 视为一个空字符串String
CommunityOwnedDate2008-08-12 04:59:35.017000000, 2024-04-01 05:36:41.380000000-考虑将 Null 的默认值设为 1970-01-01。 不需要毫秒粒度,使用 DateTimeDateTime
ClosedDate2008-09-04 20:56:44, 2024-04-06 18:49:25.393000000-考虑将 Null 的默认值设为 1970-01-01。 不需要毫秒粒度,使用 DateTimeDateTime
tip

确定某列的类型需要了解其数字范围和唯一值数量。用户可以使用简单查询 SELECT * APPLY min, * APPLY max, * APPLY uniq FROM table FORMAT Vertical 来查找所有列的范围和不同值的数量。我们建议在较小的数据子集上执行此操作,因为这可能会比较昂贵。

这将导致以下优化后的架构(就类型而言):

避免 Nullable 列

Nullable カラム (例: Nullable(String)) は UInt8 型の別のカラムを作成します。この追加のカラムは、ユーザーが Nullable カラムを操作するたびに処理される必要があります。これにより追加のストレージスペースが使用され、ほぼ常にパフォーマンスに悪影響を与えます。

Nullable カラムを避けるために、そのカラムにデフォルト値を設定することを検討してください。例えば、次の代わりに:

次のように使用します:

あなたのユースケースを考慮すると、デフォルト値が不適切な場合もあります。