本篇主题:基于3W1H原则,讲解ClickHouse,以便于后续开发实践。
中文文档:如何使用 ClickHouse 测试您的硬件 | ClickHouse Docs
ClickHouse 是俄罗斯的Yandex于2016年开源的列式存储数据库(DBMS),使用C++语言编写,主要用于在线分析处理查询(OLAP),能够使用SQL查询实时生成分析数据报告。
tips:
OLTP(on-line transaction processing)翻译为联机事务处理, OLAP(On-Line Analytical Processing)翻译为联机分析处理,从字面上来看OLTP是做事务处理,OLAP是做分析处理。从对数据库操作来看,OLTP主要是对数据的增删改,OLAP是对数据的查询。
架构分析
ClickHouse拥有完备的管理功能,所以它称得上是一个DBMS(Database Management System,数据库管理系统),而不仅是一个数据库。作为一个DBMS,它具备了一些基本功能,如下所示。
这里只列举了一些最具代表性的功能,但已然足以表明为什么Click House称得上是DBMS了。
列式存储和数据压缩,对于一款高性能数据库来说是必不可少的特性。一个非常流行的观点认为,如果你想让查询变得更快,最简单且有效的方法是减少数据扫描范围和数据传输时的大小,而列式存储和数据压缩就可以帮助我们实现上述两点。列式存储和数据压缩通常是伴生的,因为一般来说列式存储是数据压缩的前提。
列式存储相比于行式存储,列式存储在分析场景下有着许多优良的特性。
在行存模式下,数据按行连续存储,所有列的数据都存储在一个block中,不参与计算的列在IO时也要全部读出,读取操作被严重放大。
列存模式下,只需要读取参与计算的列即可,极大的减低了IO cost,加速了查询。
列存往往有着高达十倍甚至更高的压缩比,节省了大量的存储空间,降低了存储成本。
更高的压缩比意味着更小的data size,从磁盘中读取相应数据耗时更短。
官方显示,通过列存储,在某些分析场景下,能获得100倍甚至更高的加速效果。
clickhouse不仅将数据按列存储,而且按列进行计算。传统的OLTP数据库通常采用按行计算,原因是事务处理中以点查为主,SQL计算量小,实现这些技术的收益不够明显,单个SQL所涉及计算量可能极大,将每行作为一个基本单元进行处理会带来严重的性能损耗:
1)对每一行数据都要调用相应的函数,函数调用开销占比高;
2)存储层按列存储数据,在内存中按列组织,但是计算层按行处理,无法充分利用CPU Cache的预读能力,造成CPU Cache miss严重;
3)按行处理,无法利用高效的SIMD指令
ClickHouse实现了向量化执行引擎(Vectorized execution engine),对内存中的列式数据,一个batch调用一次SIMD指令(而非每一行调用一次),不仅减少函数调用次数,降低Cache miss,而且可以充分发挥SIMD指令的并行能力,大幅度缩短了计算耗时。向量执行引擎,通常能够带来数倍的性能提升。
SIMD全称Sigle Instuction Multiple Data,单指令多数据流,能够赋值多个操作数,并把它们打包在大型寄存器的一组指令集。以同步方式,在同一时刻执行同一条指令。
相比HBase和Redis这类NoSQL数据库,ClickHouse使用关系模型描述数据并提供了传统数据库的概念(数据库、表、视图和函数等)。
与此同时,ClickHouse完全使用SQL作为查询语言(支持GROUP BY、ORDER BY、JOIN、IN等大部分标准SQL)。
ClickHouse和MySQL类似,把表级的存储引擎插件化,根据表的不同需求可以设定不同的存储引擎。目前包括合并树、日志、接口和其他四大类20多种引擎。
不同的引擎决定了表数据的存储特点和表数据的操作行为:
1)决定表存储在哪里以及以何种方式存储
2)支持哪些查询以及如何支持
3)并发数据访问
4)索引的使用
5)是否可以执行多线程请求
6)数据复制参数
表引擎决定了数据在文件系统的存储方式,常用的也是官方推荐的MergeTree系列,如果需要数据副本的话可以使用ReplicateMergeTree系列,相当于MergeTree的副本版本。读取集群数据需要使用分布式表引擎Distribute。
如果说向量化执行是通过数据级并行的方式提升了性能,那么多线程处理就是通过线程级并行的方式实现了性能的提升。相比基于底层硬件实现的向量化执行SIMD,线程级并行通常由更高层次的软件层面控制。由于SIMD不适合用于带有较多分支判断的场景,ClickHouse也大量使用了多线程技术以实现提速,以此和向量化执行形成互补。
ClickHouse使用分布式分而治之的思想,利用多台服务的资源协同处理。在数据层面预先将数据分布到各太服务器,在计算层面,将数据的计算查询直接下推到数据所在服务器。在数据存取方面,既支持分区(纵向扩展,利用多线程原理),也支持分片(横向扩展,利用分布式原理),可以说是将多线程和分布式的技术应用到了极致。
HDFS、Spark、HBase和Elasticsearch这类分布式系统,都采用了Master-Slave主从架构,由一个管控节点作为Leader统筹全局。而ClickHouse则采用Multi-Master多主架构,集群中的每个节点角色对等,客户端访问任意一个节点都能得到相同的效果。这种多主的架构有许多优势,例如对等的角色使系统架构变得更加简单,不用再区分主控节点、数据节点和计算节点,集群中的所有节点功能相同。所以它天然规避了单点故障的问题,非常适合用于多数据中心、异地多活的场景。
数据分片是将数据进行横向切分,这是一种在面对海量数据的场景下,解决存储和查询瓶颈的有效手段,是一种分治思想的体现。
ClickHouse支持分片,而分片则依赖集群。每个集群由1到多个分片组成,而每个分片则对应了ClickHouse的1个服务节点。分片的数量上限取决于节点数量(1个分片只能对应1个服务节点)。
ClickHouse并不像其他分布式系统那样,拥有高度自动化的分片功能。ClickHouse提供了本地表(Local Table)与分布式表(Distributed Table)的概念。一张本地表等同于一份数据的分片。而分布式表本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件。借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。
这种设计类似数据库的分库和分表,十分灵活。例如在业务系统上线的初期,数据体量并不高,此时数据表并不需要多个分片。所以使用单个节点的本地表(单个数据分片)即可满足业务需求,待到业务增长、数据量增大的时候,再通过新增数据分片的方式分流数据,并通过分布式表实现分布式查询。
官网提供的测试:ClickBench — a Benchmark For Analytical DBMS
专家测试:http://www.clickhouse.com.cn/topic/5c453371389ad55f127768ea
官网文档:OLAP使用场景
总结:
官网在线测试链接:https://play.clickhouse.tech/?file=welcome
基础类型只有数值、字符串和时间三种类型,没有 Boolean 类型,但可以使用整型的 0 或 1 替代。
ClickHouse 的数据类型和常见的其他存储系统的数据类型对比:
MySQL |
Hive |
CLickHouse(区分大小写) |
byte |
TINYINT |
Int8 |
short |
SMALLINT |
Int16 |
int |
INT |
Int32 |
long |
BIGINT |
Int64 |
varchar |
STRING |
String |
timestamp |
TIMESTAMP |
DateTime |
float |
FLOAT |
Float32 |
double |
DOUBLE |
Float64 |
boolean |
BOOLEAN |
无 |
看官网:数据类型 | ClickHouse Docs
说明:Clickhouse的字段默认是不允许为NULL的,如果数据有可能为NULL,需要将字段定义为类似Nullable(Int64)的类型。
ClickHouse 提供了大约 28 种表引擎,各有各的用途,比如有 Log 系列用来做小表数据分析,MergeTree 系列用来做大数据量分析,而 Integration 系列则多用于外表数据集成。再考虑复制表Replicated 系列,分布式表 Distributed 等,纷繁复杂,新用户上手选择时常常感到迷惑。
ClickHouse表引擎一共分为四个系列,分别是Log、MergeTree、Integration、Special。其中包含了两种特殊的表引擎Replicated、Distributed,功能上与其他表引擎正交,根据场景组合使用。
最强大的表引擎当属 MergeTree (合并树)引擎及该系列(*MergeTree)中的其他引擎。对于大多数正式的任务,推荐使用MergeTree 族中的引擎。因为只有合并树系列的表引擎才支持主键索引、数据分区、数据副本和数据采样这些特性,同时也只有此系列的表引擎支持ALTER相关操作。
Log、Special、Integration 主要用于特殊用途,场景相对有限。MergeTree 系列才是官方主推的存储引擎,支持几乎所有 ClickHouse 核心功能。
存储引擎,去查阅官方文档
一共分为四个系列,分别是Log、MergeTree、Integration、Special。其中包含了两种特殊的表引擎Replicated、Distributed,功能上与其他表引擎正交。表引擎(即表的类型)决定了:
(1)数据的存储方式和位置,写到哪里以及从哪里读取数据
(2)支持哪些查询以及如何支持。
(3)并发数据访问。
(4)索引的使用(如果存在)。
(5)是否可以执行多线程请求。
(6)数据复制参数。
1、Log系列
Log系列表引擎功能相对简单,主要用于快速写入小表(1百万行左右的表),然后全部读出的场景。
几种Log表引擎的共性是:
它们彼此之间的区别是:
2、Integration系列
该系统表引擎主要用于将外部数据导入到ClickHouse中,或者在ClickHouse中直接操作外部数据源。
3、Special系列
Special系列的表引擎,大多是为了特定场景而定制的。这里也挑选几个简单介绍,不做详述。
4、MergeTree系列
Log、Special、Integration主要用于特殊用途,场景相对有限。MergeTree系列才是官方主推的存储引擎,支持几乎所有ClickHouse核心功能。
1、一般情况下使用MergeTree引擎即可。
2、子类引擎使用
特殊功能:发生在分区合并时
ReplacingMergeTree:解决数据去重问题,有相同主键无法去重的问题,主要基于order by后的字段进行去重。
CollapsingMergeTree:数据删除问题,以增代删。缺陷:多线程情况下,乱序写入,无法删除。
VersionedCollapsingMergeTree:数据删除问题,以增代删。解决CollapsingMergeTree缺陷
SummingMergeTree:特定数据合并问题,主要基于order by后的字段进行sum聚合。
AggregatingMergeTree:特定数据合并问题,主要基于order by后的字段进行AggregateFunction 类型的聚合
5、Distributed 分布式引擎
Distributed 分布式引擎本身不存储数据, 但可以在多个服务器上进行分布式查询。
参见知识库其他文档:clickhouse的语法特性 vs maxcomputer
ClickHouse中至少存在两种类型的函数
官网资料:Functions | ClickHouse Docs
自定义函数实现,知识库《ClickHouse源码:函数&自定义函数》
ck适合的数据模型
ck数仓数据模型采用星型模型搭建,星型架构是一种非正规化的结构,多维数据集的每一个维度都直接与事实表相连,不存在渐变维度,所以数据有一定冗余。因为有冗余,所以很多统计不需要做外部的关联查询,因此一般情况下效率比雪花模型高。
说明:使用星型模型搭建即最大限度的允许数据冗余,减少必要的外表连接,提高sql效率
基于clickHouse数据架构
定义引擎类别MySQL,直接进行操作。
clear_dx_user_group_001 数据源是mysql;export_dx_user_group_001数据源是clickhouse。
核心表:
CREATE TABLE IF NOT EXISTS ctuserbasecoredb.app_dx_portrait_tag_umap_1d ( dw_gmt_create DateTime COMMENT '日期', ds FixedString(8) COMMENT '数据分区 yyyymmdd', tag_id String COMMENT '标签id', tag_value String COMMENT '标签值', us AggregateFunction(groupBitmap,UInt64) COMMENT '用户uid集合形成bitmapping' )engine=AggregatingMergeTree() partition by ds order by (tag_id,tag_value) -- 指定数据生命周期为7天,数据备份,到指定卷或者磁盘 TTL dw_gmt_create + INTERVAL 7 DAY -- 索引粒度,默认8192 SETTINGS index_granularity = 8192 COMMENT '标签-标签聚合用户表-全量表(tag->uidbitmap)';
聚集:
INSERT INTO TABLE ctuserbasecoredb.app_dx_portrait_tag_umap_1d select now() AS dw_gmt_create ,'${before_date_str}' AS ds ,t.tag_id ,t.tag_value ,groupBitmapState(t.bit_map_id) AS us from ( select s1.tag_id ,s1.out_user_id ,s1.tag_value ,s2.bit_map_id from( SELECT tag_id ,out_user_id ,tag_value FROM ctuserbasecoredb.dwb_dx_portrait_user_lni_act_1d where ds = '${before_date_str}' UNION ALL select tag_id ,out_user_id ,tag_value FROM ctuserbasecoredb.dwb_dx_portrait_user_attr_1d where ds = '${before_date_str}' )s1 join ( select out_user_id,bit_map_id from ctuserbasecoredb.dim_dx_user_bitmap_id_df where ds = '${before_date_str}' )s2 on s1.out_user_id = s2.out_user_id )t group by t.tag_id,t.tag_value;
关键SQL:
insert into app_dx_portrait_group_tag_aggregation_1d select now() as dw_gmt_create ,'{self.ds_partition}' as ds ,'{group_id}' as group_id ,s2.out_user_id as out_user_id ,s2.tag_detail ,now() as base_time from ( select arrayJoin(bitmapToArray(t.us)) as id from ({group_tag_conditions_sql})t) s1 join ( select out_user_id,bit_map_id,tag_detail from dim_dx_user_bitmap_id_df where ds = '20211013' )s2 on s1.id = s2.bit_map_id
group_tag_conditions_sql:
select arrayJoin(bitmapToArray(t.us)) as id from ( select groupBitmapOrState(us) as us from ( select groupBitmapAndState(us) as us from ( select us from app_dx_portrait_tag_umap_1d where ds='20211013' and tag_id='DX_01_AB_0001' and tag_value = 'true' UNION ALL select us from app_dx_portrait_tag_umap_1d where ds='20211013' and tag_id='DX_01_AA_0001' and tag_value = 'true' UNION ALL select us from app_dx_portrait_tag_umap_1d where ds='20211013' and tag_id='DX_01_AA_0002' and tag_value = '电信' UNION ALL select groupBitmapOrState(us) as us from app_dx_portrait_tag_umap_1d where ds='20211013' and tag_id='DX_01_AA_0003' and tag_value in ('未知', '浙江', '上海', '北京', '湖北') ) ) )
知识库文档:ClickHouse 监控平台
监控 | ClickHouse Docs
这是官网给出的方案:基于prometheus + grafana生态。
测试环境:http://172.31.28.31:3000/d/10Bw94W7z/clickhousece-shi-huan-jing?orgId=1
单表查询:
多表查询:
insert into hits_v2 select a.* from hits_v1 a where a. CounterID in (select CounterID from visits _v1
insert into table hits_v2 select a.* from hits_v1 a left join visits_v 2 b on a. CounterID=b. CounterID;
基本语法:
EXPLAIN [AST | SYNTAX | PLAN | PIPELINE] [setting = value, ...] SELECT ... [FORMAT ...]
➢ PLAN:用于查看执行计划,默认值。
➢ AST :用于查看语法树;
➢ SYNTAX:用于优化语法;
➢ PIPELINE:用于查看PIPELINE 计划。
注:PLAN 和PIPELINE 还可以进行额外的显示设置,如上参数所示。
-- 先做一次查询 SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'hello') FROM numbers(10); -- 查看语法优化 EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'hello') FROM numbers(10); -- 开启三元运算符优化 SET optimize_if_chain_to_multiif = 1; -- 再次查看语法优化 EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'hello') FROM numbers(10); -- 返回优化后的语句 SELECT if(number = 1, 'hello', if(number = 2, 'world', 'hello')) FROM numbers(10);