ClickHouse快速入门

ClickHouse快速入门上篇

CK官网 什么是ClickHouse? | ClickHouse Docs


1. ClickHouse是什么

1.1 一句话介绍ClickHouse

ClickHouse 的全称是是Click Stream,Data WareHouse,简称ClickHouse、CH、CK,是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库,使用 C++ 语言编写,主要用于在线分析处理查询(OLAP)能够使用 SQL 查询实时生成分析数据报告。

ps:olap数据库的特点就是擅长做一次插入多次查询的操作,而不适合做删除和修改

1.2 ClickHouse的优缺点

优点:

1) 支持完备的SQL操作

几乎覆盖了标准 SQL 的大部分语法,包括 DDL 和 DML,以及配套的各种函数,用户管理及权限管理,数据的备份与恢复。

2) 列式存储与数据压缩

大数据常用的存储方式,优点:易压缩、聚合、计数等

3)高吞吐的写入能力

ClickHouse 在数据导入时全部是顺序 append 写,写入后数据段不可更改,在后台 compaction 时也是多个段 merge sort 后顺序写回磁盘。官方公开 benchmark 测试显示能够达到 50MB-200MB/s 的写入吞吐能力,按照每行 100Byte 估算,大约相当于 50W-200W 条/s 的写入速度。 4) 丰富的表引擎

ClickHouse 和 MySQL 类似,把表级的存储引擎插件化,根据表的不同需求可以设定不同 的存储引擎。目前包括合并树、日志、接口和其他四大类 20 多种引擎。

5) 并行处理

ClickHouse 将数据划分为多个 partition,每个 partition 再进一步划分为多个 index granularity(索引粒度),然后通过多个 CPU核心分别处理其中的一部分来实现并行数据处理。 在这种设计下,单条 Query 就能利用整机所有 CPU。极致的并行处理能力,极大的降低了查 询延时。 所以,ClickHouse 即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端 就是对于单条查询使用多 cpu,就不利于同时并发多条查询。所以对于高 qps 的查询业务, ClickHouse 并不是强项。

6) 查询速度快

CK的查询速度快是基于单表的查询的速度快,而对于多表的join查询时,速度没有优势。

总结:CK对于单表的查询速度非常快,单条 Query 就能利用整机所有 CPU,因此不太适合高QPS的查询,进一步来讲,CK不适合存储初始数据的数据库,更加契合大量的已经处理过的宽表的存储。

缺点:

1)不支持事务,不支持真正的删除/更新;

2)不支持高并发,官方建议qps为100,可以通过修改配置文件增加连接数,但是在服务器足够好的情况下;

3)SQL满足日常使用80%以上的语法,join比较特殊;最新版已支持类似SQL的join,但性能不好;

2. CK的基本数据类型

简单来讲,CK的数据类型的使用方法同其他数据库,但是在写法上有点差异。

2.1 整型

2.1.1 有符号整型

Int8 - [-128 : 127]

Int16 - [-32768 : 32767]

Int32 - [-2147483648 : 2147483647]

Int64 - [-9223372036854775808 : 9223372036854775807]

直接是int加上位数,同int,bigint,tinyint等。一般来讲肯定是int32和int64用的居多。

2.1.2 无符号整型

UInt8 - [0 : 255]

UInt16 - [0 : 65535]

UInt32 - [0 : 4294967295]

UInt64 - [0 : 18446744073709551615]

2.2 浮点型

float32,float64

说明:浮点型会存在精度丢失的问题,所以使用场景一般数据值比较小,不涉及大量的统计计算,精度要求不高的时候。比如 保存商品的重量。通常下都会使用Decimal类型表示小数。

2.3 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 s 标识小数位

使用场景: 一般金额字段、汇率、利率等字段为了保证小数点精度,都使用 Decimal 进行存储。

2.4 字符串

1)String

字符串可以任意长度的。它可以包含任意的字节集,包含空字节。

2)FixedString(N)

固定长度 N 的字符串,N 必须是严格的正自然数。当服务端读取长度小于 N 的字符 串时候,通过在字符串末尾添加空字节来达到 N 字节长度。 当服务端读取长度大于 N 的 字符串时候,将返回错误消息。 与 String 相比,极少会使用 FixedString,因为使用起来不是很方便。

2.5 枚举类型

2.6 数组类型

2.7 时间类型

目前 ClickHouse 有三种时间类型

➢ Date 接受年-月-日的字符串比如 ‘2019-12-16’

➢ Datetime 接受年-月-日 时:分:秒的字符串比如 ‘2019-12-16 20:50:10’

➢ Datetime64 接受年-月-日 时:分:秒.亚秒的字符串比如‘2019-12-16 20:50:10.66’ 日期类型,用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值。

还有很多其他的数据类型,不常用:函数 | ClickHouse Docs

3. ClickHouse的表引擎

表引擎是 ClickHouse 的一大特色。可以说, 表引擎决定了如何存储表的数据。还决定了:

➢ 数据的存储方式和位置,写到哪里以及从哪里读取数据。

➢ 支持哪些查询以及如何支持。

➢ 并发数据访问。

➢ 索引的使用(如果存在)。

➢ 是否可以执行多线程请求。

➢ 数据复制参数。

表引擎的使用方式就是必须显式在创建表时定义该表使用的引擎,以及引擎使用的相关参数,引擎的名称大小写敏感。

3.1 TinyLog

以列文件的形式保存在磁盘上,不支持索引,没有并发控制。一般保存少量数据的小表, 生产环境上作用有限。可以用于平时练习测试用。

create table t_tinylog ( id String, name String) engine=TinyLog;

3.2 Memory

内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。 读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的性能表现(超过 10G/s)。 一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太 大(上限大概 1 亿行)的场景。

3.3 MergeTree(重要)

ClickHouse 中最强大的表引擎当属 MergeTree(合并树)引擎及该系列(*MergeTree) 中的其他引擎,支持索引和分区,地位可以相当于 innodb 之于 Mysql。而且基于 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);

3.3.1 partition by(可选)

同其他数据库分区作用一致,减少查找范围,增加并行度

注意:

任何一个批次的数据写入都会产生一个临时分区,不会纳入任何一个已有的分区。写入 后的某个时刻(大概 10-15 分钟后),ClickHouse 会自动执行合并操作(等不及也可以手动 通过 optimize 执行),把临时分区的数据,合并到已有分区中。

optimize table xxxx final;

3.3.2 primary key (可选)

ClickHouse 中的主键,和其他数据库不太一样,它只提供了数据的一级索引但是却不是唯一约束。这就意味着是可以存在相同 primary key 的数据的。

3.3.3 order by(必选)

order by 设定了分区内的数据按照哪些字段顺序进行有序保存。

order by 是 MergeTree 中唯一一个必填项,甚至比 primary key 还重要,因为当用户不 设置主键的情况,很多处理会依照 order by 的字段进行处理(比如后面会讲的去重和汇总)。

要求:主键必须是 order by 字段的前缀字段。

比如 order by 字段是 (id,sku_id) 那么主键必须是 id 或者(id,sku_id)

3.3.4 二级索引(跳数索引)

大多数情况下我们都是按照主键查询的,但是如果我们查询一个非主键字段呢?或者通过一个虚拟列查询呢?例如表中有列a和b,经常需要根据 where a * b == value 来查询。此时稀疏索引就失效了,就需要全表扫描,查询效率无疑大大降低,ClickHouse MergeTree针对这种情况也给出了优化方案,就是二级索引,又叫跳数索引(Data Skipping Indexes)。

老版本不支持二级索引,需要把set allow_experimental_data_skipping_indices=1; 新版本支持了。

语法:

create table t_order_mt2( 
    id UInt32, 
    sku_id String, 
    total_amount Decimal(16,2), 
    create_time Datetime, 
    INDEX a sku_id TYPE minmax GRANULARITY 5 ) 
engine =MergeTree 
partition by toYYYYMMDD(create_time)
primary key (id) -- 这行可以不要
order by (id, sku_id); -- order by 也是建立一级索引的过程,
set

因为primary key 已经是建立了一个一级索引,再添加一个索引,就是二级索引了

INDEX a total_amount意思:将total_amount构建索引,并且命名为a

Type:索引的类型,通常使用的minmax,就是只记录这一段数据的最小值和最大值。

set(max_row)记录max_row里面非重复的值,通常为那些重复率很高的字段建立这样的索引,比如,年龄

GRANULARITY 5 :简单来讲就是一级索引粒度的粒度,把5个一级索引块在合并一次作为二级索引块。

默认提供两种索引,稀疏索引和跳数索引,
​
根据索引所覆盖的行数产生索引标记来记录数据的区间信息
​
**稀疏索引**
​
按主键或者排序键进行排序后保存,默认粒度8192行。稀疏索引只需要使用少量的索引标记就可以记录大量数据的区间位置信息,这样索引文件足够小,常驻内存取用更快
​
 **跳数索引**
​
由数据的聚合产生(Max/min等)数据经过间隔力度构建索引后,跳数索引会根据索引粒度对数据进行汇总(跳跃)。
​
跳数索引的粒度其实相当于对已构建的索引的合并。

Mysql建立索引原则:CK应该可适用

mysql建立索引原则:MySQL~索引设计原则:适合创建索引的11种情况、不适合创建索引的7种情况_Salute-Y的博客-CSDN博客_mysql中不能创建索引的语句

3.3.5 数据TTL(感觉很鸡肋)

TTL 即 Time To Live,MergeTree 提供了可以管理数据表或者列的生命周期的功能。可以设置某个表或者字段的生命周期是多少,到时间了,这个字段或者表的数据就会清空。

语法:

字段级:

create table t_order_mt3( 
    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 time + interval 10 SECOND

time建议使用分区时间,10 SECOND 也可以使用 1 DAY , 1 WEEK, 1HOUR等

表级:

alter table t_order_mt3 MODIFY TTL create_time + INTERVAL 10 SECOND;

3.4 ReplacingMergeTree

ReplacingMergeTree 是 MergeTree 的一个变种,它存储特性完全继承 MergeTree,只是多了一个去重的功能。 尽管 MergeTree 可以设置主键,但是 primary key 其实没有唯一约束 的功能。如果你想处理掉重复的数据,可以借助这个 ReplacingMergeTree。

1)去重时机

数据的去重只会在合并的过程中出现。合并会在未知的时间在后台进行,所以你无法预 先作出计划。有一些数据可能仍未被处理。

2)去重范围

如果表经过了分区,去重只会在分区内部进行去重,不能执行跨分区的去重。

所以 ReplacingMergeTree 能力有限, ReplacingMergeTree 适用于在后台清除重复的数 据以节省空间,但是它不保证没有重复的数据出现。

建表语句:

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() 填入的参数为版本字段,重复数据保留版本字段值最大的。

比如:填入的是create_time字段意思是保留create_time最大,也计算最新的数据。

如果不填版本字段,默认按照插入顺序保留最后一条。

3.5 SummingMergeTree(自我感觉很鸡肋)

对于不查询明细,只关心以维度进行汇总聚合结果的场景。如果只使用普通的MergeTree 的话,无论是存储空间的开销,还是查询时临时聚合的开销都比较大。 ClickHouse 为了这种场景,提供了一种能够“预聚合”的引擎 SummingMergeTree

更多信息参考官网。。

4. CK的语法

简单来讲和sql语法类似

但有以下差异:ClickHouse下篇第二节CK的语句优化会介绍

1)GROUP BY 操作增加了 with rollup\with cube\with total

例如: group by id,sku_id with rollup;

增加学习任务,作用不大。

2)支持in的子查询

如 in (select xxxx)

hive sql并不支持,但是支持left simi join 。

3)直到21年9月才支持窗口函数,老版本不支持。

4)不支持自定义函数

其他有待发现。。。。。。

ClickHouse快速入门下篇


1. CK建表时需考虑的事情

1.1 存储时间数据

能用DateTime类型就用DateTime,不要用String或者int

例如:

create table t_type2( 
    id UInt32, 
    sku_id String,
    total_amount Decimal(16,2) , 
    create_time Int32 ) 
engine =ReplacingMergeTree(create_time) 
partition by toYYYYMMDD(toDate(create_time)) –-需要转换一次,否则报错 
primary key (id) order by (id, sku_id);

这里create_time的类型最好不要用Int32,因为在每存一条数据的时候都需要toDate()转换一次,影响效率

1.2低基数的String使用LowCardinality类型的String

语法

 CREATE TABLE test.user_event_lowcard_str (
   user_id Int64,
   event_type LowCardinality(String)
 ) ENGINE = MergeTree()
 ORDER BY user_id;

这篇帖子写的超鸡好:聊聊ClickHouse中的低基数(LowCardinality)类型_LittleMagics的博客-CSDN博客

1.2 空值存储

官方已经指出 Nullable 类型几乎总是会拖累性能,因为存储 Nullable 列时需要创建一个 额外的文件来存储 NULL 的标记,并且 Nullable 列无法被索引。因此除非极特殊情况,应直接使用字段默认值表示空,或者自行指定一个在业务中无意义的值(例如用-1 表示没有商品 ID)。

1.3 分区和索引

1.4 Index_granularity 和 TTL

CREATE TABLE order_info
(
    `oid`   UInt64,                     --订单ID
    `buyer_nick`    String,     --买家ID
    `seller_nick`   String,     --店铺ID
    `payment`   Decimal64(4), --订单金额
    `order_status`  UInt8,         --订单状态
    ...
    `gmt_order_create`  DateTime, --下单时间
    `gmt_order_pay` DateTime,         --付款时间
    `gmt_update_time` DateTime,     --记录变更时间
    INDEX oid_idx (oid) TYPE minmax GRANULARITY 32
)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(gmt_order_create)         --以天为单位分区
ORDER BY (seller_nick, gmt_order_create, oid) --排序键
PRIMARY KEY (seller_nick, gmt_order_create)     --主键
SETTINGS index_granularity = 8192 

最后一行:设置稀疏索引的间隔行数,8192是默认值,这个值也不建议修改

1.5 CK常见的配置项

1.5.1 CPU资源

配置 描述
background_pool_size 后台线程池的大小,merge 线程就是在该线程池中执行,该线程池 不仅仅是给 merge 线程用的,默认值 16,允许的前提下建议改成 c pu 个数的 2 倍(线程数)。
background_schedule_pool_size 执行后台任务(复制表、Kafka 流、DNS 缓存更新)的线程数。默 认 128,建议改成 cpu 个数的 2 倍(线程数)。
background_distributed_schedule_ pool_size 设置为分布式发送执行后台任务的线程数,默认 16,建议改成 cpu 个数的 2 倍(线程数)。
max_concurrent_queries 最大并发处理的请求数(包含 select,insert 等),默认值 100,推荐 1 50(不够再加)~300。
max_threads 设置单个查询所能使用的最大 cpu 个数,默认是 cpu 核数

1.5.2 内存资源

配置 描述
max_memory_usage 此参数在 users.xml 中,表示单次 Query 占用内存最大值,该值可 以设置的比较大,这样可以提升集群查询的上限。 保留一点给 OS,比如 128G 内存的机器,设置为 100GB。
max_bytes_before_external_group_ by 一般按照 max_memory_usage 的一半设置内存,当 group 使用内 存超过阈值后会刷新到磁盘进行。 因为 clickhouse 聚合分两个阶段:查询并及建立中间数据、合并中 间数据,结合上一项,建议 50GB。
max_bytes_before_external_sort 当 order by 已使用 max_bytes_before_external_sort 内存就进行 溢写磁盘(基于磁盘排序),如果不设置该值,那么当内存不够时直接 抛错,设置了该值 order by 可以正常完成,但是速度相对存内存来 说肯定要慢点(实测慢的非常多,无法接受)。
max_table_size_to_drop 此参数在 config.xml 中,应用于需要删除表或分区的情况,默认是 50GB,意思是如果删除 50GB 以上的分区表会失败。建议修改为 0, 这样不管多大的分区表都可以删除。

2. CK的语句优化

2.1 Prewhere 替代 where

Prewhere 和 where 语句的作用相同,用来过滤数据。不同之处在于 prewhere 只支持 *MergeTree 族系列引擎的表,首先会读取指定的列数据,来判断数据过滤,等待数据过滤 之后再读取 select 声明的列字段来补全其余属性。

当查询列明显多于筛选列时使用 Prewhere 可十倍提升查询性能,Prewhere 会自动优化 执行过滤阶段的数据读取方式,降低 io 操作。

下面是自动将where转化为prewhere的开关,默认为1,是打开的

set optimize_move_to_prewhere=0;

2.2 uniqCombined 替代 count(distinct xx)

性能可提升 10 倍以上,uniqCombined 底层采用类似 HyperLogLog 算法实现,能接收 2% 左右的数据误差,可直接使用这种去重方式提升查询性能。Count(distinct )会使用 uniqExact 精确去重。

这个帖子不错:clickhouse数据去重函数介绍(count distinct)_我要去学习了的博客-CSDN博客_clickhouse count distinct

2.3 使用in代替join

正例:select a.* from hits_v1 a where a. CounterID in (select CounterID from visits_v1);

反例:select a.* from hits_v1 a left join visits_v1 b on a. CounterID=b. CounterID;

同理在hive sql中使用left semi join 代替 join

使用in 或者left semi join 的效率虽高一些,但是很明显的在使用条件是具有一定的局限性。

2.4大小表 JOIN(重要)

多表 join 时要满足小表在右的原则,右表关联时被加载到内存中与左表进行比较, ClickHouse 中无论是 Left join 、Right join 还是 Inner join 永远都是拿着右表中的每一条记录 到左表中查找该记录是否存在,所以右表必须是小表。

2.5 分布式表使用 GLOBAL

分布式表是一种表引擎,建立分布式表的语法如下:

create table test_log_all on cluster ck_cluster
(
    totalDate Date,
    unikey    String
)
    engine = Distributed('ck_cluster', 'test', 'test_log', rand());

两张分布式表上的 IN 和 JOIN 之前必须加上 GLOBAL 关键字,右表只会在接收查询请求 的那个节点查询一次,并将其分发到其他节点上。如果不加 GLOBAL 关键字的话,每个节点 都会单独发起一次对右表的查询,而右表又是分布式表,就导致右表一共会被查询 N²次(N 是该分布式表的分片数量),这就是查询放大,会带来很大开销。

2.3 列裁剪和分区裁剪

简单来讲就是不要select *, 以及多使用where

2.4 谓词下推

反例:select a. * b. * from a join b on a.id = b.id where b.age > 20;

正例:select a. * , c. * from a join (select * from b where age > 20) c on a.id = c.id;

这个hive sql内部已经优化掉了,但不知道CK也没有优化掉

3. 物化视图

普通视图不保存数据,保存的仅仅是查询语句,查询的时候还是从原表读取数据,可以将普通视图理解为是个子查询。物化视图则是把查询的结果根据相应的引擎存入到了磁盘或内存中, 它就是一张表,它也像是一张时刻在预计算的表,创建的过程它是用了一个特殊引擎。

通俗来讲,物化视图可以将查询的结果保存在一张特殊的表中,一旦原表的数据增加了,那么物化视图的表也会触发相应的计算。

创建语法如下:

CREATE MATERIALIZED VIEW hits_mv 
ENGINE=SummingMergeTree 
PARTITION BY toYYYYMM(EventDate) 
ORDER BY (EventDate, intHash32(UserID)) 
AS SELECT 
    UserID, 
    EventDate, 
    count(URL) as ClickCount, 
    sum(Income) AS IncomeSum 
FROM hits_test 
WHERE EventDate >= '2014-03-20'

一旦hits_test表中增加了数据,在这个hits_mv视图里面也会增加相应的聚合值。

感觉很鸡肋,详情见下面帖子:

详解clickhouse中的物化视图_立二拆四i的博客-CSDN博客_clickhouse 物化视图

你可能感兴趣的:(大数据,数据库)