ClickHouse 的全面解析:高性能列式存储数据库

ClickHouse 是由俄罗斯搜索引擎公司 Yandex 于 2016 年开源的列式存储数据库管理系统(DBMS),使用 C++ 语言编写。它主要用于在线分析处理查询(OLAP),能够通过 SQL 查询 实时生成分析数据报告,广泛应用于大数据分析领域。

1. ClickHouse 的特点

ClickHouse 作为一款高性能的列式数据库,具有以下显著特点:

1.1 列式存储

行式存储列式存储是两种不同的数据组织方式。以下以一个简单的表为例,比较两者的存储结构及优缺点。

行式存储

在行式存储中,数据按行组织在磁盘上:

行式存储示意图

优点

  • 查询单个记录时效率高,适合 OLTP(联机事务处理)场景。
  • 新增数据速度快,直接在末尾追加。

缺点

  • 当需要查询某一列的所有数据(如统计年龄)时,需要扫描整个表,效率低下。
  • 数据压缩效果较差,因为同一列的数据类型相同,列式存储更易于压缩。
列式存储

在列式存储中,数据按列组织在磁盘上:

列式存储示意图

优点

  • 对单列的聚合查询(如求和、计数)效率高,适合 OLAP(联机分析处理)场景。
  • 同一列的数据类型相同,便于数据压缩,提高存储效率。

缺点

  • 新增数据较慢,需要进行寻址和重组。
  • 随机写入性能较差,不适合高频更新的场景。

1.2 多样化引擎

ClickHouse 的表引擎具有高度的插件化,类似于 MySQL 的存储引擎。根据不同的表需求,可以选择不同的存储引擎。目前包括MergeTree 系列Log 系列MemoryTinyLog 等 20 多种引擎。

1.3 高吞吐写入能力

ClickHouse 采用类 LSM Tree(Log-Structured Merge Tree)的结构,将写入操作与合并操作分离。数据写入时先写入日志文件和内存缓存(MemStore),然后定期在后台进行合并(Compaction)操作。

特点

  • 顺序写入:数据导入时全部是顺序追加,极大地提高了写入性能,尤其在 HDD 上表现出色。
  • 高吞吐:官方基准测试显示,ClickHouse 的写入吞吐能力可达 50MB-200MB/s,约相当于 50W-200W 条/s 的写入速度。
  • 不可变数据段:写入后数据段不可更改,更新操作通过版本号标记,实现数据的无锁更新。

1.4 数据分区与线程级并行

ClickHouse 通过数据分区和多线程并行处理,极大地提升了查询性能。

分区

  • 目的:避免全局扫描,优化查询速度。
  • 实现:将数据划分为多个分区,每个分区再细分为索引粒度(index granularity)。
  • 并行处理:多个 CPU 核心分别处理不同的分区,实现线程级并行查询。

优点

  • 极致并行:单条查询可以利用整机所有 CPU 核心,显著降低查询延时。
  • 高效分布式处理:将数据分散到不同分区,提升整体查询效率。

缺点

  • 单条查询的多 CPU 利用:不利于同时并发多条查询,ClickHouse 在高 QPS(每秒查询次数)的场景下表现一般。
  • 适用场景限制:不适合作为初始存储,适合处理已经清洗过的大量宽表数据。

2. 数据类型

ClickHouse 支持多种数据类型,满足不同的数据存储和处理需求。

2.1 整型

固定长度的整型,包括有符号和无符号类型。

有符号整型范围(-2ⁿ⁻¹ ~ 2ⁿ⁻¹-1):

  • Int8 - [-128, 127]
  • Int16 - [-32768, 32767]
  • Int32 - [-2147483648, 2147483647]
  • Int64 - [-9223372036854775808, 9223372036854775807]

无符号整型范围(0 ~ 2ⁿ⁻¹-1):

  • UInt8 - [0, 255]
  • UInt16 - [0, 65535]
  • UInt32 - [0, 4294967295]
  • UInt64 - [0, 18446744073709551615]

2.2 浮点型

  • Float32 - 单精度浮点数
  • Float64 - 双精度浮点数

建议:尽可能以整数形式存储数据,避免浮点运算中的四舍五入误差。例如,使用毫秒作为时间单位。

2.3 布尔型

ClickHouse 没有单独的布尔类型,可以使用 UInt8 类型,取值限制为 0 或 1。

2.4 Decimal 型

有符号的浮点数,在加、减、乘运算中保持精度。对于除法,最低有效数字会被丢弃(不舍入)。

声明方式

  • Decimal32(s):相当于 Decimal(9-s, s),有效位数 1~9
  • Decimal64(s):相当于 Decimal(18-s, s),有效位数 1~18
  • Decimal128(s):相当于 Decimal(38-s, s),有效位数 1~38

2.5 字符串

1. String
  • 任意长度的字符串,包含任意字节集,支持空字节。
2. FixedString(N)
  • 固定长度 N 的字符串,N 必须为正整数。
  • 特点
    • 读取长度小于 N 的字符串时,末尾补齐空字节。
    • 读取长度大于 N 的字符串时,返回错误。
  • 注意:由于使用不便,较少使用 FixedString,推荐使用 String

2.6 枚举类型

包括 Enum8Enum16 类型,用于保存 'string' = integer 的对应关系。

  • Enum8:使用 StringInt8 的对应关系。
  • Enum16:使用 StringInt16 的对应关系。

2.7 时间类型

ClickHouse 支持以下三种时间类型:

  • Date:格式如 ‘2019-12-16’,仅包含年月日。
  • DateTime:格式如 ‘2019-12-16 20:50:10’,包含时、分、秒。
  • DateTime64:格式如 ‘2019-12-16 20:50:10.66’,包含亚秒级精度。

2.8 数组

  • Array(T):由 T 类型元素组成的数组,T 可为任意类型,包括数组类型。但不推荐使用多维数组,因为 ClickHouse 对多维数组的支持有限,无法在 MergeTree 表中存储多维数组。

示例

CREATE TABLE example_array (
    id UInt32,
    names Array(String)
) ENGINE = MergeTree()
ORDER BY id;

3. 表引擎

表引擎是 ClickHouse 的一大特色,决定了表的数据存储方式、查询支持、并发访问等特性。

3.1 表引擎的使用

表引擎决定了数据的存储方式和位置、支持的查询类型、并发控制、索引使用及数据复制参数等。创建表时必须显式定义使用的引擎及相关参数。注意:引擎名称大小写敏感。

3.2 TinyLog

  • 特点
    • 以列文件形式保存在磁盘上。
    • 不支持索引没有并发控制
  • 适用场景
    • 保存少量数据的小表。
    • 生产环境应用有限。

3.3 Memory

  • 特点
    • 数据以未压缩的原始形式存储在内存中。
    • 服务器重启后数据会丢失。
    • 读写操作不互斥,不支持索引。
    • 查询性能极高(超过 10G/s)。
  • 适用场景
    • 测试环境。
    • 需要极高性能且数据量较小(上限约 1 亿行)的场景。

3.4 MergeTree

MergeTree 是 ClickHouse 中最强大的表引擎,支持索引和分区,类似于 MySQL 的 InnoDB 引擎。基于 MergeTree,衍生出多个功能丰富的变种。

创建示例

CREATE TABLE t_order_mt (
    id UInt32,
    sku_id String,
    total_amount Decimal(16,2),
    create_time DateTime
) ENGINE = MergeTree
PARTITION BY toYYYYMMDD(create_time)
PRIMARY KEY (id)
ORDER BY (id, sku_id);

MergeTree 存储示意图

特点

  • Primary Key:用于数据的一级索引,但不具备唯一约束。
  • Partitioning:支持按指定字段分区,优化查询性能。
  • Order By:设定数据的有序存储方式,影响查询效率。
  • 索引:支持稀疏索引,提高数据定位效率。
  • 数据 TTL:支持数据生命周期管理,自动清理过期数据。
3.4.1 分区(Partition By)
  • 作用降低扫描范围,优化查询速度。
  • 未设置:数据集中在一个分区(目录名为 all)。
  • 分区目录:数据存储在不同分区目录中,便于管理。
  • 并行处理:跨分区查询时,按分区并行处理,提升性能。
  • 数据写入与合并
    • 数据写入后生成临时分区,随后自动或手动合并到已有分区。

    • 示例

      OPTIMIZE TABLE t_order_mt FINAL;
      

      分区合并示意图

3.4.2 主键(Primary Key)
  • 特点
    • 提供数据的一级索引。
    • 不具备唯一约束,允许存在相同主键的数据。
  • 作用:根据查询条件中的 WHERE 子句,通过主键进行二分查找,快速定位数据所在的索引粒度,避免全表扫描。
  • 索引粒度(Index Granularity):默认为 8192,指在稀疏索引中两个相邻索引对应数据的间隔。
    • 优化建议:不建议修改索引粒度,除非存在大量重复值。
3.4.3 Order By
  • 作用:设定分区内数据的有序存储字段。
  • 要求主键必须是 Order By 字段的前缀字段
    • 例如,ORDER BY (id, sku_id),则主键可以是 id(id, sku_id)
  • 重要性Order By 字段决定了数据的存储顺序,是优化查询性能的关键。
3.4.4 二级索引

创建示例

CREATE TABLE t_order_mt2 (
    id UInt32,
    sku_id String,
    total_amount Decimal(16,2),
    create_time DateTime,
    INDEX a total_amount TYPE minmax GRANULARITY 5
) ENGINE = MergeTree
PARTITION BY toYYYYMMDD(create_time)
PRIMARY KEY (id)
ORDER BY (id, sku_id);

参数说明

  • GRANULARITY N:设定二级索引相对于一级索引粒度的粒度。
3.4.5 数据 TTL

TTL(Time To Live):用于管理数据的生命周期,自动删除过期数据。

  • 列级别 TTL

    CREATE TABLE t_order_mt2 (
        id UInt32,
        sku_id String,
        total_amount Decimal(16,2) TTL create_time + INTERVAL 10 SECOND,
        create_time DateTime
    ) ENGINE = MergeTree
    PARTITION BY toYYYYMMDD(create_time)
    PRIMARY KEY (id)
    ORDER BY (id, sku_id);
    
    • 效果:指定字段数据到期后自动清零。
  • 表级别 TTL

    ALTER TABLE t_order_mt3 MODIFY TTL create_time + INTERVAL 10 SECOND;
    
    • 要求:涉及判断的字段必须为 DateDateTime 类型,推荐使用分区的日期字段。
    • 支持的时间周期SECONDMINUTEHOURDAYWEEKMONTHQUARTERYEAR

3.5 ReplacingMergeTree

ReplacingMergeTreeMergeTree 的一个变种,继承了 MergeTree 的所有特性,并新增了去重功能

特点

  • 去重时机:仅在合并过程中进行,无法预知具体时间,部分重复数据可能暂时存在。
  • 去重范围:仅在同一分区内进行,无法跨分区去重。
  • 适用场景:适用于后台清除重复数据以节省空间,但不保证完全无重复。

创建示例

CREATE TABLE t_order_rmt (
    id UInt32,
    sku_id String,
    total_amount Decimal(16,2),
    create_time DateTime
) ENGINE = ReplacingMergeTree(create_time)
PARTITION BY toYYYYMMDD(create_time)
PRIMARY KEY (id)
ORDER BY (id, sku_id);
  • 参数说明ReplacingMergeTree() 中的参数为版本字段,用于保留版本字段值最大的记录。
  • 默认行为:若未指定版本字段,按插入顺序保留最后一条记录。

手动合并

OPTIMIZE TABLE t_order_rmt FINAL;

ReplacingMergeTree 合并示意图

测试结论

  • 使用 Order By 字段作为唯一键。
  • 去重仅限于同一分区内部。
  • 仅在同一批次插入或分片合并时进行去重。
  • 重复数据按版本字段值保留,若版本字段相同则按插入顺序保留最后一笔。

3.6 SummingMergeTree

SummingMergeTree 适用于不查询明细,仅关心按维度汇总聚合结果的场景。相比普通 MergeTreeSummingMergeTree 能够“预聚合”数据,减少存储空间和查询时的聚合开销。

创建示例

CREATE TABLE t_order_smt (
    id UInt32,
    sku_id String,
    total_amount Decimal(16,2),
    create_time DateTime
) ENGINE = SummingMergeTree(total_amount)
PARTITION BY toYYYYMMDD(create_time)
PRIMARY KEY (id)
ORDER BY (id, sku_id);

SummingMergeTree 聚合示意图

手动合并

OPTIMIZE TABLE t_order_smt FINAL;

SummingMergeTree 合并示意图

测试结论

  • SummingMergeTree() 中指定的列作为汇总数据列。
  • 可填写多列,必须为数字列;若不填,默认所有非维度且为数字列的字段为汇总列。
  • Order By 的列作为维度列。
  • 其他列按插入顺序保留第一行。
  • 不同分区的数据不会被聚合。
  • 仅在同一批次插入或分片合并时进行聚合。

4. 总结

ClickHouse 作为一款高性能的列式存储数据库,凭借其卓越的查询性能、多样化的表引擎和灵活的数据分区策略,成为大数据分析领域的重要工具。通过合理设计数据模型和表结构,结合 ClickHouse 的优化特性,能够高效处理海量数据,满足实时分析和复杂查询的需求。

主要优势

  • 高性能:列式存储和多线程并行处理使得 ClickHouse 在 OLAP 场景下表现优异。
  • 灵活的数据模型:丰富的数据类型和表引擎选择,满足多样化的应用需求。
  • 高可扩展性:支持分布式部署,能够轻松扩展以应对数据增长。

注意事项

  • 行键设计:合理设计行键,避免数据倾斜和热点问题。
  • 表引擎选择:根据具体需求选择合适的表引擎,充分利用其特性。
  • 资源管理:确保集群资源充足,避免因资源不足影响性能。

通过深入理解和合理应用 ClickHouse 的各项特性,能够充分发挥其在大数据分析中的潜力,助力业务决策和数据驱动的发展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值