ClickHouse 是由俄罗斯搜索引擎公司 Yandex 于 2016 年开源的列式存储数据库管理系统(DBMS),使用 C++ 语言编写。它主要用于在线分析处理查询(OLAP),能够通过 SQL 查询 实时生成分析数据报告,广泛应用于大数据分析领域。
1. ClickHouse 的特点
ClickHouse 作为一款高性能的列式数据库,具有以下显著特点:
1.1 列式存储
行式存储和列式存储是两种不同的数据组织方式。以下以一个简单的表为例,比较两者的存储结构及优缺点。
行式存储
在行式存储中,数据按行组织在磁盘上:
优点:
- 查询单个记录时效率高,适合 OLTP(联机事务处理)场景。
- 新增数据速度快,直接在末尾追加。
缺点:
- 当需要查询某一列的所有数据(如统计年龄)时,需要扫描整个表,效率低下。
- 数据压缩效果较差,因为同一列的数据类型相同,列式存储更易于压缩。
列式存储
在列式存储中,数据按列组织在磁盘上:
优点:
- 对单列的聚合查询(如求和、计数)效率高,适合 OLAP(联机分析处理)场景。
- 同一列的数据类型相同,便于数据压缩,提高存储效率。
缺点:
- 新增数据较慢,需要进行寻址和重组。
- 随机写入性能较差,不适合高频更新的场景。
1.2 多样化引擎
ClickHouse 的表引擎具有高度的插件化,类似于 MySQL 的存储引擎。根据不同的表需求,可以选择不同的存储引擎。目前包括MergeTree 系列、Log 系列、Memory、TinyLog 等 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~9Decimal64(s)
:相当于Decimal(18-s, s)
,有效位数 1~18Decimal128(s)
:相当于Decimal(38-s, s)
,有效位数 1~38
2.5 字符串
1. String
- 任意长度的字符串,包含任意字节集,支持空字节。
2. FixedString(N)
- 固定长度 N 的字符串,N 必须为正整数。
- 特点:
- 读取长度小于 N 的字符串时,末尾补齐空字节。
- 读取长度大于 N 的字符串时,返回错误。
- 注意:由于使用不便,较少使用
FixedString
,推荐使用String
。
2.6 枚举类型
包括 Enum8
和 Enum16
类型,用于保存 'string' = integer
的对应关系。
Enum8
:使用String
和Int8
的对应关系。Enum16
:使用String
和Int16
的对应关系。
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);
特点:
- 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;
- 要求:涉及判断的字段必须为
Date
或DateTime
类型,推荐使用分区的日期字段。 - 支持的时间周期:
SECOND
、MINUTE
、HOUR
、DAY
、WEEK
、MONTH
、QUARTER
、YEAR
- 要求:涉及判断的字段必须为
3.5 ReplacingMergeTree
ReplacingMergeTree
是 MergeTree
的一个变种,继承了 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;
测试结论:
- 使用
Order By
字段作为唯一键。 - 去重仅限于同一分区内部。
- 仅在同一批次插入或分片合并时进行去重。
- 重复数据按版本字段值保留,若版本字段相同则按插入顺序保留最后一笔。
3.6 SummingMergeTree
SummingMergeTree
适用于不查询明细,仅关心按维度汇总聚合结果的场景。相比普通 MergeTree
,SummingMergeTree
能够“预聚合”数据,减少存储空间和查询时的聚合开销。
创建示例:
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);
手动合并:
OPTIMIZE TABLE t_order_smt FINAL;
测试结论:
- 以
SummingMergeTree()
中指定的列作为汇总数据列。 - 可填写多列,必须为数字列;若不填,默认所有非维度且为数字列的字段为汇总列。
- 以
Order By
的列作为维度列。 - 其他列按插入顺序保留第一行。
- 不同分区的数据不会被聚合。
- 仅在同一批次插入或分片合并时进行聚合。
4. 总结
ClickHouse 作为一款高性能的列式存储数据库,凭借其卓越的查询性能、多样化的表引擎和灵活的数据分区策略,成为大数据分析领域的重要工具。通过合理设计数据模型和表结构,结合 ClickHouse 的优化特性,能够高效处理海量数据,满足实时分析和复杂查询的需求。
主要优势:
- 高性能:列式存储和多线程并行处理使得 ClickHouse 在 OLAP 场景下表现优异。
- 灵活的数据模型:丰富的数据类型和表引擎选择,满足多样化的应用需求。
- 高可扩展性:支持分布式部署,能够轻松扩展以应对数据增长。
注意事项:
- 行键设计:合理设计行键,避免数据倾斜和热点问题。
- 表引擎选择:根据具体需求选择合适的表引擎,充分利用其特性。
- 资源管理:确保集群资源充足,避免因资源不足影响性能。
通过深入理解和合理应用 ClickHouse 的各项特性,能够充分发挥其在大数据分析中的潜力,助力业务决策和数据驱动的发展。