Clickhouse入门上手
前言
工作需要开始接触大数据相关的内容,对我来说都是全新的东西,所以从头开始慢慢学吧。确认也打乱了自己生涯规划里的学习方向,但没办法,工作为重。所以中间件的学习和研究放到明年再继续吧。
先贴一下我的学习资料
B站尚硅谷视频教程
官方文档
官方文档还是很贴心的,有中文翻译版本和PDF,但有些翻译比较生硬,如果觉得怪怪的,可以切成英文试试。
1.ClickHouse是什么
ClickHouse是俄罗斯的Yandex开源的列式存储数据库,用于在线分析处理查询(OLAP On-Line Analytical Processing),不同于常用的MySQL 联机事务处理 OLTP(on-line transaction processing)。
1.1 列式存储的特点
以下图为例
行式存储的数据结构为
优点:查单条的所有字段时,可以通过一次磁盘查找顺序读取就可以了
缺点:获取所有的数据单个字段时,需要不停的查找或者全表扫描
列式存储的数据结构为
优点:
1-对于列的聚合、计数等统计操作优于行式存储
2-每列的数据类型相同,更容易进行数据压缩存储
缺点:插入慢,比如插入一条id = 4的数据,需要找到每个字段的末尾进行插入
2.ClickHouse的特点
2.1 高吞吐写入能力
ClickHouse 采用类LSM Tree的结构,数据写入后定期在后台Compaction。通过类LSM Tree的结构,数据插入时顺序写,写入后数据不可修改,在后台Compaction时也是多个段merge sort后顺序写回磁盘。官方压测结果 50M-200M/s
LSM Tree简单来说就是写的时候先往内存写,达到阈值时再写入磁盘。查询的时候先查内存,没有命中再去查磁盘。所以基于LSM Tree实现的HBase写性能高于MySQL,读性能低于MySQL。
2.2 数据分区与线程级并行
ClickHouse将数据分为多个partition,每个partition再进一步划分为多个index granularit(索引粒度),然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理。所以单条查询SQL就能利用到所有的cpu。
2.3 PRIMARY KEY和ORDER BY
ClickHouse的主键不同于MySQL,ClickHouse的主键可以重复即不唯一,且索引类型为稀疏索引,数据的有序则依赖于ORDER BY。所以,在ClickHouse的建表语句中,PARTITION BY和PRIMARY KEY都是选填,反而是ORDER BY为必填。这里还需要注意一点,PRIMARY KEY必须是ORDER BY的前缀字段,如ORDER BY(id,sku_id) 那么PRIMARY KEY必须是id或者(id,sku_id)。
3.多样化引擎
ClickHouse有合并树、日志、接口、其他四大类共20多种引擎,目前我用到的主要是合并树。
3.1 ReplacingMergeTree
使用ReplacingMergeTree为表引擎,则该表的数据会根据ORDER BY字段进行分区内去重。但是数据的去重只会在数据合并期间进行。虽然可以调用 OPTIMIZE 语句发起的合并,但是OPTIMIZE 语句会引发对数据的大量读写,导致该时间段内的该表不可用。
3.2 SummingMergeTree
使用SummingMergeTree为表引擎,则该表内所有具有相同主键的行合并为一行,且会对SummingMergeTree指定的字段进行统计操作。如果主键的组合方式使得单个键值对应于大量的行,则可以显著的减少存储空间并加快数据查询的速度。
4.遇到且解决的问题
ClickHouse的版本问题妙不可言,目前遇到两个比较有意思的问题。
1-测试环境21.7.6.39版本可以使用AggregateFunction(groupBitmap,Int32),而沙盒环境21.2.3.15版本却不行。解决方法是把Int32换成UInt32即可,但是要注意UInt32只能聚合UInt32的字段,聚合Int32会报错。
2-ClickHouse不同版本的时间的最大值不同。21.7.6.39版本的时间最大值为2149-12-31,插入21.2.3.15版本后会变成1970-01-01。
4.上手实践
CREATE table a_replacingMergeTree(
id Int32,
sku_id Int32,
created DateTime
) engine = ReplacingMergeTree(created) --填入字段为版本字段 如果不填,默认按照顺序插入的最后一条
PARTITION BY toYYYYMM(created)
PRIMARY KEY (id)
ORDER BY (id, sku_id)
truncate a_replacingMergeTree;
optimize table a_replacingMergeTree final;
INSERT INTO a_replacingMergeTree values
(100,001,'2021-11-06 00:00:00'),
(100,002,'2021-11-06 01:00:00'),
(101,001,'2021-11-06 02:00:00'),
(101,001,'2021-11-06 03:00:00'),
(101,001,'2021-11-07 03:00:00');
INSERT INTO a_replacingMergeTree values (100,001,'2021-11-06 00:00:00');
INSERT INTO a_replacingMergeTree values (100,002,'2021-11-06 01:00:00');
INSERT INTO a_replacingMergeTree values (101,001,'2021-11-06 02:00:00');
INSERT INTO a_replacingMergeTree values (101,001,'2021-11-06 03:00:00');
INSERT INTO a_replacingMergeTree values (101,001,'2021-11-07 03:00:00');
CREATE table a_summingMergeTree(
id Int32,
sku_id Int32,
totol_money Decimal(16,2),
created DateTime
) engine = SummingMergeTree(totol_money)
PARTITION BY toYYYYMM(created)
PRIMARY KEY (id)
ORDER BY (id, sku_id);
truncate a_summingMergeTree;
optimize table a_summingMergeTree final;
INSERT INTO a_summingMergeTree values (100,001,1000,'2021-11-06 00:00:00');
INSERT INTO a_summingMergeTree values (100,002,1000,'2021-11-06 01:00:00');
INSERT INTO a_summingMergeTree values (101,001,1000,'2021-11-06 02:00:00');
INSERT INTO a_summingMergeTree values (101,001,1000,'2021-11-06 03:00:00');