存储设计——如何优化 ClickHouse 索引(一)

本文深入探讨ClickHouse的存储设计与索引机制,包括主键索引、Granule概念、Mark文件的作用等,揭示ClickHouse高效查询背后的秘密。

Keypoint

  1. ClickHouse 索引与其他 RDMS 区别
  2. 稀疏主键索引及其构建
  3. ClickHouse 索引最佳实践

ClickHouse 的索引设计

  Whole data:     [---------------------------------------------]
  CounterID:      [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
  Date:           [1111111222222233331233211111222222333211111112122222223111112223311122333]
  Marks:           |      |      |      |      |      |      |      |      |      |      |
                  a,1    a,2    a,3    b,3    e,2    e,3    g,1    h,2    i,1    i,3    l,3
  Marks numbers:   0      1      2      3      4      5      6      7      8      9      10

从文件目录看 ClickHouse 存储设计

/var/lib/clickhouse
- DataBase
  - Table
    - Parts all/Partition_key xxxx (分区键影响parts 数量)
      - checksum.txt (例如因为同步导致all broken parts to remove错误)
      - columns.txt (列及对应格式的数据)
      - count.txt (当前数据块条数)
      - default_compression_codec.txt(压缩格式,默认 LZ4)
      - primary.idx 主键索引 (可以与 ORDER BY 不同)
      - Column.bin(数据文件,压缩后可能是 1:6,2:7,3:4....,在 Compact 模式下,只有一个 bin)
      - Column.mk2 (好像都一样大小,像是 Mark的缩写?)

从存储文件夹中可以看到,分级大概是
表 -> Parts -> 主键索引(idx)、

ClickHouse 中,**索引字段本身可以包含空值(NULL)**,但是否允许为空取决于你使用的表引擎、数据类型定义以及索引类型(主要是主键索引或二级索引),并且需要注意些关键限制和行为。 --- ### 回答问题: > **ClickHouse索引字段可以为空吗?** ✅ **可以**,但需要满足以下条件: 1. 字段的数据类型必须显式声明为 `Nullable(...)`。 2. 主键(Primary Key)或排序键(ORDER BY)字段**不推荐使用 `Nullable` 类型**,因为 ClickHouse 对这类字段的处理存在限制和性能问题。 3. 在大多数生产场景中,**主键/排序键字段应避免为 NULL**。 --- ### 原因与解释: #### 1. ClickHouse 支持 `Nullable` 类型 你可以这样定义个可能为空的索引字段: ```sql CREATE TABLE example_table ( id UInt64, name Nullable(String), created_date DateTime ) ENGINE = MergeTree ORDER BY (name, id); ``` 上面的例子中,`name` 是排序键的部分,并且是 `Nullable(String)` —— 这在语法上是**允许的**。 #### 2. 为什么官方不推荐将 Nullable 字段用于主键或排序键? - **性能影响**:`Nullable` 列会额外存储个位图(null byte map),导致比较操作变慢。 - **排序行为复杂化**: - 在排序时,`NULL` 值被视为“最小值”。 - 例如,在升序排序中,所有 `NULL` 会排在最前面。 - **索引跳过率下降**:稀疏索引(Sparse Index)依赖有序性进行数据块跳过。如果大量 `NULL` 值打乱分布,会导致索引效率降低。 - **某些函数行为异常**:如 `minmax` 索引对 `Nullable` 列的支持不如非空列稳定。 #### 3. 物化视图和分区键中的空值 - 分区键(`PARTITION BY`)字段也**不应为 `Nullable`**,否则可能导致分区逻辑混乱或写入失败。 - 同样建议用默认值代替(如空字符串、0 或特殊标记值)。 --- ### 最佳实践建议: | 场景 | 是否建议允许为空 | |------|----------------| | 主键 / 排序键字段 | ❌ 不建议 | | 普通查询字段(带二级索引) | ✅ 可以,但谨慎使用 | | 分区键字段 | ❌ 避免使用 Nullable | ✅ 推荐替代方案: ```sql -- 更好的设计:避免主键为 Null,用 0 或特殊值表示缺失 CREATE TABLE user_log ( user_id UInt64 DEFAULT 0, -- 而不是 Nullable(UInt64) event_time DateTime, device_type String DEFAULT '' ) ENGINE = MergeTree ORDER BY (user_id, event_time); ``` --- ### 总结 虽然 ClickHouse **语法上允许索引字段为 NULL**(只要类型是 `Nullable(T)`),但由于性能、排序致性及索引效率的问题,**强烈建议不要将主键、排序键或分区键设为可为空**。应尽量通过业务逻辑或默认值来规避 `NULL` 的使用。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值