《ClickHouse原理解析与应用实践》

由于业务上要用到ClickHouse,因此花了两三天看了此书,讲的不是特别深,但是挺适合作为ClickHouse使用者的入门,常用的使用姿势和应用需要了解的一些原理都有讲到。

ClickHouse在线文档:(https://clickhouse.com/docs/zh/)[https://clickhouse.com/docs/zh/]

第一部分,背景篇

OLTP→OLAP

ROLAP,直接使用关系模型构建。海量数据问题。

MOLAP,使用多维数组的形式保存数据,其核心思想是借助预先聚合结果,使用空间换取时间的形式最终提升查询性能。数据膨胀和滞后性问题。

HOLAP,混合上面两种

数据立方体概念

MyISAM引擎使用B+树结构存储索引,而数据则使用另外单独的存储文件;InnoDB引擎使用B+树同时存储索引和数据,数据直接挂载在叶子节点中。

HBase使用LSM树索引结构,发源于Google的BigTable。LSM本质上可以看做是将一颗大树拆成了许多小树。先在内存中构建一颗小树,小树构建过程中会进行排序,小树的数量达到某个阈值输入磁盘生成一小段数据。在每个数据段中,数据局部有序。

发展历程:mysql→Metrage→OLAPServer→ClickHouse

不适用场景:

1、不支持事务

2、不擅长根据主键按行粒度进行查询(虽然支持)

3、不擅长按行删除数据(虽然支持)

ClickHouse拥有完备的管理功能,是一个DBMS(数据库管理系统),而不仅仅是一个数据库。

列式存储和数据压缩通常是伴生的,ClickHouse默认使用L4Z算法压缩,在Yandex的生成环境,数据总体压缩比可以达到8:1。

ClickHouse目前利用SSE4.2指令集实现向量化执行

ClickHouse架构设计中的核心模块:(图)

Data Streams,Server,Parsers、Interpreters,Storages,Columns,DataTypes,Functions

ClickHouse的1个节点只能拥有1个分片,也就是说如果要实现1分片、1副本,则至少需要部署2个服务节点。

分片只是一个逻辑概念,其物理承载还是由副本承担的。

SIMD被广泛应用于文本转换、数据过滤、数据解压和JSON转换等场景。

第二部分,基础篇

基础类型:数值、字符串、时间(没有Boolean)

数值类型分为:整数(Int)、浮点数(Float)、定点数(Decimal)

字符串类型分为:String、FixedString、UUID

时间类型:DateTime、DateTime64、Date,最高精度是秒

复合类型:数组(Array)、元组(Tuple)、枚举(Enum)、嵌套(Nested)

特殊类型:

Nullable,只能和基础类型搭配使用,不能作为索引字段

Domain,IPv6、IPv6

数据库引擎类型:

Ordinary:默认引擎

Dictionary:字典引擎,自动为所有数据字典创建他们的数据表

Memory:内存引擎,临时数据,只停留在内存中

Lazy:日志引擎,只能使用Log系列的表引擎

MySQL:MySQL引擎,会自动拉取远端MySQL中的数据,并为它们创建MySQL表引擎的数据表

三种基本的建表方法:常规定义、复制其他表结构、SELECT字句形式

表字段支持三种默认值表达式的定义方法:DEFAULT、MATERIALIZED、ALIAS

临时表 create temporary table …,只支持Memory表引擎,优先级大于普通表

分区表,数据分区(partition)和数据分片(shard)是完全不同的两个概念。数据分区是针对本地数据而言的,是数据的一种纵向切分。而数据分片是数据的一种横向切分。

只有合并树(MergeTree)家族系列的表引擎才支持数据分区,通过partition by指定分区键

普通视图:create view view_name as select …

不存储任何数据,只是一层单纯的SELECT查询映射,简化查询、明晰语义的作用,对查询性能不会有任何增强。

物化视图:create materialized view table_name to name engine=xxx populate as select …

支持表引擎,如果源表被写入新数据,那么物化视图也会同步更新。populate修饰符觉得初始化策略,是否连带源表中已存在的数据一并导入。

物化视图本质上是一张特殊的数据表

目前只有MergeTree、Merge、Distribute三类表引擎支持ALTER查询

RENAME可以用于移动数据表,仅限单个节点范围内

分布式DDL,只需要在DDL后面加上 ON CLUSTER cluster_name

数据写入方式:

常规:INSERT INTO table_name VALUES (xxx)

指定格式:INSERT INTO table_name FORMAT format_name data_set

SELECT子语句:INSERT INTO table_name SELECT …

数据删除与修改

ClickHouse的Delete和Update能力被成为Mutation查询,它可以看做ALTER语句的变种,是一种很重的操作,不支持事务,执行是异步的后台过程,语句提交之后就会立即返回,执行进度需要通过system.mutaitons系统表查询。每执行一条ALTER DELETE语句,都会在mutations系统表中生成一条对应的执行计划。数据删除过程是以数据表的每个分区目录为单位,将所有目录重写为新的目录,新目录的命名规则是在原有名称上加上system.mutaitions.block_numbers.number。数据在重写过程中会将需要删除的数据去掉。旧的数据目录不会立即删除,而是会被标记成非激活状。等到MergeTree引擎下次合并动作触发时,这些非激活目录才会真正从物理意义上删除。

ClickHouse多种实时更新方法总结:https://www.modb.pro/db/197765

ClickHouse拥有内置和扩展两类数据字典,目前内置的只有YM字典,扩展的有7种类型,其中flat、hashed、range_hashed依次拥有最高的性能。

第三部分,原理篇

分区规则,分区目录合并规则

一级索引,稀疏索引

二级索引,跳数索引

如果把MergeTree比作一本书,primary.idx一级索引就好比这本书的一级章节目录,.bin(数据存储文件)中的数据就好比这本书中的文字,.mrk(数据标记文件)会为一级章节目录和具体的文字之间建立关联。

数据TTL,分为列级别和表级别,默认合并频率1天,可以通过merge_with_ttl_timeout来控制

多路径存储策略:默认,JBOD,HOT/COLD

ReplacingMergeTree处理逻辑

1、使用ORDER BY排序键作为判断重复数据的逻辑

2、只有在合并分区的时候才会触发删除重复数据的逻辑

3、以数据分区为单位删除重复数据。当分区合并时,同一分区内的重复数据才会被删除;不同分区之间的重复数据不会被删除

4、在进行数据去重时,因为分区内的数据已经基于ORDER BY进行排序,所以能够找到那些相邻的重复数据

5、数据去重策略有两种:

如果没有设置ver版本号,则保留同一组重复数据中的最后一行

如果设置了ver版本号,则保留同一组重复数据中ver字段取值最大的那一行

如果同时声明了ORDER BY和PRIMARY KEY,MergeTree会强制要求PRIMARY KEY列字段必须是ORDER BY的前缀

SummingMergeTree处理逻辑

1、用ORDER BY排序键作为聚合数据的条件Key

2、只有在合并分区的时候才会触发汇总的逻辑

3、以数据分区为单位来聚合数据。当分区合并时,同一数据分区内聚合Key相同的数据会被合并汇总,而不同分区之间的数据则不会被汇总

4、如果在定义引擎时指定了columns汇总列(非主键的数值类型字段),则SUM汇总这些列字段,如果未指定,则聚合所有非主键的数值类型字段

5、在进行数据汇总时,同一分区内,相同聚合Key的多行数据会合并成一行。其中,汇总字段会进行SUM计算,对于那些非汇总字段,则会使用第一行数据的取值

6、支持嵌套结构,但列字段名称必须以Map后缀结尾。嵌套类型中,默认以第一个字段作为聚合Key。除第一个字段外,任何名称以Key、Id或Type为后缀结尾的字段,都将和第一个字段组成复合Key

AggregatingMergeTree处理逻辑

1、用ORDER BY排序键作为聚合数据的条件Key

2、使用AggregateFunction字段类型定义聚合函数的类型以及聚合的字段

3、只有在合并分区的时候才会触发汇总的逻辑

4、以数据分区为单位来聚合数据。当分区合并时,同一数据分区内聚合Key相同的数据会被合并汇总,而不同分区之间的数据则不会被汇总

5、在进行数据计算时,因为分区内的数据已经基于ORDER BY进行排序,所以能够找到那些相邻且拥有相同聚合Key的数据

6、在聚合数据时,同一分区内,相同聚合Key的多行数据会合并成一行。对于那些非主键、非AggregateFunction类型字段,则会使用第一行数据的取值

7、AggregateFunction类型的字段使用二进制存储,在写入数据时,需要调用State函数;而在查询数据时,则需要调用相应的Merge函数。其中,*表示定义时使用的聚合函数

8、AggregatingMergeTree通常作为物化视图的表引擎,与普通MergeTree搭配使用

另外还有CollapsingMergeTree、VersionedCollapsingMergeTree、GraphiteMergeTree,以及整个MergeTree家族引擎的关系

其他常见表引擎

外部存储类型:HDFS、MySQL、JDBC、Kafka、File

内存类型:Memory、Set、Join、Buffer

日志类型:TinyLog、StripeLog、Log

接口类型:Merge、Dictionary、Distributed

其他类型:Live View、Null、URL

对于列式存储数据库,应尽量避免使用select *查询

ClickHouse支持的查询语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[WITH expr_list|(subquery)]
SELECT [DISTINCT] expr_list
[FROM [db.]table | (subquery) | table_function] [FINAL]
[SAMPLE sample_coeff]
[ARRAY JOIN ...]
[GLOBAL] [ANY|ALL|ASOF] [INNER|LEFT|RIGHT|FULL|CROSS] [OUTER|SEMI|ANTI] JOIN (subquery)|table (ON <expr_list>)|(USING <column_list>)
[PREWHERE expr]
[WHERE expr]
[GROUP BY expr_list] [WITH TOTALS]
[HAVING expr]
[ORDER BY expr_list] [WITH FILL] [FROM expr] [TO expr] [STEP expr]
[LIMIT [offset_value, ]n BY columns]
[LIMIT [n, ]m] [WITH TIES]
[UNION ALL ...]
[INTO OUTFILE filename]
[FORMAT format]

WITH:定义变量、调用函数、定义子查询、在子查询中重复使用WITH

FROM:

SAMPLE:数据采样

ARRAY JOIN:支持INSERT和LEFT两种JOIN策略,同时对多个数组字段进行ARRAY JON操作时,查询的计算逻辑是按行合并而不是产生笛卡尔积

JOIN:按精度可以分为ALL、ANY、ASOF三种,按类型可以分为外连接、内连接、交叉连接三种

WHERE和PREWHERE:PREWHERE只能用于MergeTree系列引擎,它首先会读取PREWEHRE指定的列字段,用于数据过滤的条件判断。待数据过滤后再读取SELECT声明的列字段以补全其余属性。性能更高。

GROUP BY:SELECT如果声明了列字段,则只能使用聚合键包含的字段。WITH ROLLUP,WITH CUBE,WITH TOTALS三种额外汇总信息

HAVING:需要和GROUP BY同时使用,不能单独使用。在聚合计算之后实现二次过滤数据

ORDER BY:全局排序,可以通过NULL FIRST/LAST修饰NULL值的排序

LIMIT BY:和LIMIT不同,运行于ORDER BY之后和LIMIT之前,能够按指定分组,最多返回前n行数据,常用于TOP N的查询场景

LIMIT:

SELCT:

DISTINCT:

UNION ALL:联合左右两边的两组子查询,将结果一并返回。

Replicated前缀表引擎才能应用副本的能力,增加了zk监听的部分,通过zk存储mutaions操作日志

Distributed表引擎,本身不存储任何数据,它能够作为分布式表的一层透明代理。

多副本的路由规则,四种负载均衡算法:random、nearest_hostname,in_order、first_or_random

多分片查询的核心流程:查询各个分片数据,合并返回结果

使用Global优化分布式查询

服务监控可以从两个方面入手:

系统表:metrics、events、asynchronous_metrics

查询日志:query_log、query_thread_log、part_log、text_log、metric_log