Clickhouse入门篇

一、简介

ClickHouse(Click Stream,Data Warehouse)是一个用于联机分析[OLAP[[Online Analytical Processing])的列式数据库管理系统。

最核心的特点是极致压缩率和极速查询性能。同时,ClickHouse支持SQL查询,且查询性能好,特别是基于大宽表的聚合分析查询性能非常优异,比其他分析型数据库速度快一个数量级。

适用场景:
●✓ 网页及APP分析(Web and App analytics)
●✓ 广告网络以及广告投放(Advertising networks and RTB)
●✓ 通信领域(Telecommunications)
●✓ 在线电商以及金融(E-commerce and finance)
●✓ 信息安全(Information security)
●✓ 监控遥测(Monitoring and telemetry)
●✓ 时序数据(Time series)
●✓ 商业分析(Business intelligence)
●✓ 网友(Online games)
●✓ 物联网(Internet of Things)

二、特性

●列式存储
对于分析类查询,大部分事件只需要读取特定的列。在列式数据库中你可以只读取你需要的数据。例如,如果只需要读取100列中的5列,这将最少减少20倍的I/O消耗。
由于数据总是打包成批量读取的,所以压缩是非常容易的。且数据按列分别存储这也更容易压缩。这进一步降低了I/O的体积。
由于数据被压缩,减少了IO的读取量,也使更多的数据更容易被系统缓存(CPU磁盘速度对比)。

行式:处于同一行中的数据总是被物理的存储在一起。
列式:来自不同列的值被单独存储,来自同一列的数据被存储在一起。

应用于OLAP场景
行式
在这里插入图片描述

列式
在这里插入图片描述

●极致压缩(与列式存储相辅相成,数据类型一致,压缩性能更高)
数据压缩比率是由压缩算法、列的数据类型、数据重复度等决定的

  • 压缩算法:在磁盘空间和CPU消耗之间进行不同权衡的高效通用压缩编解码器之外,还提供针对特定类型数据的专用编解码器。
  • 列的数据类型:列式存储决定了数据具有相同的数据类型和现实语义。
  • 数据重复度:同一个列字段的数据,因为它们具有相同的数据类型和现实语义,重复项的可能性自然就更高。

压缩的本质是按照一定步长对数据进行匹配扫描,将重复的部分进行编码转换。
例: 压缩前:abcdefghi_bcdefghi 压缩后:abcdefghi_(9,8)
真实的压缩算法要比这个复杂许多,但压缩的本质就是如此。数据中的重复项越多,则压缩率越高;压缩率越高,则数据体量越小;而数据体量越小,在网络中传输的速度则越快,并且对网络带宽和磁盘 IO 的压力也就越小。
Clickhouse默认使用LZ4 算法压缩。

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

●多样化引擎(决定了数据的存放和读取方式,从而也就决定了IO效率)
ClickHouse和MySQL类似,把表级的存储引擎插件化,根据表的不同需求可以设定不同的存储引擎。目前包括合并树、日志、接口和其他四大类20多种引擎,每一种引擎都有着各自的特点,根据实际业务场景需求选择合适的。

●高吞吐的写入能力(合理使用的情况下)
批量异步写入,按照数据分片的方式合并数据,列式存储所以是顺序的IO操作。
Clickhouse采用类LSM Tree(更多是一种数据结构的思想:将随机操作变为顺序操作)的结构,数据写入后定期在后台 Compaction。

通过类 LSM tree的结构,ClickHouse 在数据导入时全部是顺序 append 写,写入后数据段不可更改,在后台compaction 时也是多个段 merge sort 后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力。
官方公开 benchmark 测试显示能够达到 50MB-200MB/s 的写入吞吐能力,按照每行100Byte 估算,大约相当于 50W-200W 条/s 的写入速度。
LSM 相比 B+ 树能提高写性能的本质原因是:外存——无论磁盘还是 SSD,其随机读写都要慢于顺序读写。

●多节点多核并行处理
单条SQL可以完全利用整机CPU,数据分片,化整为零,从而利用多核的优势达到极致的并行处理能力,极大的降低了查询延时。ClickHouse 将数据划分为多个 partition,每个 partition 再进一步划分为多个 index granularity(索引粒度),然后通过多个 CPU 核心分别处理其中的一部分来实现并行数据处理。

Mysql MyISAM or InnoDB 是单SQL单线程模式

●向量化执行引擎
为了高效的使用CPU,数据不仅仅按列存储,同时还按向量(列的一部分)进行处理,这样可以更加高效地使用CPU。
向量化执行可以简单地看做成一种消除程序中的循环所做的优化,依赖于寄存器硬件层面的特性,实现数据的并行计算

[ClickHouse 目前使用 SSE 4.2 指令集实现向量化执行

三、限制

●不支持事务(大数据量的事务会降低系统吞吐)。

●不支持直接的Update/Delete操作,需要使用alter来更新或删除,性能不是很好,异步操作,会阻塞对表的所有读写操作。
Clickhouse:ALTER TABLE [db.]table UPDATE column1 = expr1 [, …] WHERE filter_expr

Mysql:UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]

●不支持高并发的操作,并发低,并行高。ClickHouse的目标是保证单条查询的响应足够快,单条SQL可以跑满一大半的CPU,多shard多parts的并行操作,一条SQL有可能把CPU、内存、磁盘IO拉到很高的利用度。

四、表引擎

MergeTree系列

4.1 MergeTree引擎

主要用于海量数据分析,支持数据分区、存储有序、主键索引、稀疏索引、数据TTL等。MergeTree支持所有ClickHouse SQL语法,虽然有主键,但是主键是为了快速读取数据用的,不保证唯一性。
ClickHouse中最常用也是最基础的表引擎为MergeTree,在它的功能基础上添加特定功能就构成了MergeTree系列引擎【附加的操作只有在分片合并的时候才会执行】

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],

INDEX index_name1 expr1 TYPE type1(…) GRANULARITY value1,
INDEX index_name2 expr2 TYPE type2(…) GRANULARITY value2
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr [DELETE|TO DISK ‘xxx’|TO VOLUME ‘xxx’], …]
[SETTINGS name=value, …]

●ENGINE 引擎名和参数 ENGINE = MergeTree(). MergeTree 引擎没有参数
●ORDER BY 排序键(没有使用PRIMARY KEY 显式指定的主键,默认使用排序键为主键,不需要排序,使用ORDER BY tuple() .)
●PARTITION BY 分区键
●PRIMARY KEY 主键( 选择与排序键不同的主键,可选项。默认主键跟排序键(由 ORDER BY 子句指定)相同)
●SETTINGS 控制 MergeTree 行为的额外参数。例:index_granularity-索引粒度。索引中相邻的『标记』间的数据行数。默认值8192 。

4.2 ReplacingMergeTree引擎

用来解决当出现主键相同的记录时,保留哪一条数据的问题。ReplacingMergeTree就是在MergeTree的基础上加入了去重的功能,根据特定的算法,决定仅保留主键相同的哪一条数据。但它仅会在合并分区时,去删除重复的数据,不同分区的数据不会合并
可以手动optimize,大数据量情况下影响系统性能。

OPTIMIZE TABLE [db.]name [ON CLUSTER cluster] [PARTITION partition | PARTITION ID ‘partition_id’] [FINAL] [DEDUPLICATE]

示例:ENGINE = ReplacingMergeTree([ver])

4.3 AggregatingMergeTree引擎

预先聚合引擎的一种,用于提升聚合计算的性能。在后台合并数据分片时,按照预先定义的条件聚合数据,将主键相同的多行数据根据预先定义的聚合函数聚合为一行数据,以二进制的格式存入表中。
ClickHouse只在分片合并时才会进行数据的预先聚合,而执行时机无法预测,所以可能存在部分数据已经被预先聚合、部分数据尚未被聚合的情况。因此,在执行聚合计算时,SQL中仍需要使用GROUP BY子句。在预先聚合时,ClickHouse会对主键列之外的其他所有列进行预聚合。

AggregatingMergeTree的语法比较复杂,需要结合物化视图或ClickHouse的特殊数据类型AggregateFunction一起使用。在insert和select时,也有独特的写法和要求:写入时需要使用-State语法,查询时使用-Merge语法。

示例:ENGINE = AggregatingMergeTree()

4.4 ReplicatedMergeTree引擎

支持数据副本。在 MergeTree 能力的基础之上增加了分布式协同的能力,其借助 zookeeper 的消息日志广播功能,实现了副本实例之间的数据同步功能。
●ReplicatedMergeTree
●ReplicatedSummingMergeTree
●ReplicatedReplacingMergeTree
●ReplicatedAggregatingMergeTree
●ReplicatedCollapsingMergeTree
●ReplicatedVersionedCollapsingMergeTree
●ReplicatedGraphiteMergeTree

副本是表级别的,不是整个服务器级的。所以,服务器里可以同时有复制表和非复制表。
副本不依赖分片。每个分片有它自己的独立副本。

Special系列

4.5 Distributed引擎

分布式引擎本身不存储数据。作为分布式表的一层透明代理,在集群内部自动开展数据的写入分发以及查询路由工作。

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],

) ENGINE = Distributed(cluster, database, table[, sharding_key[, policy_name]])
[SETTINGS name=value, …]

●cluster 集群名
●database 数据库名
●table 表名
●sharding_key 分片key
●policy_name 规则
示例:Distributed(‘ck_cluster’, ‘oushudan’, ‘crs_events_local’, rand())

数据查询流程:

  1. Client 发送查询语句到 ClickHouse 代理
  2. 根据负载均衡策略,选取一个请求节点(集群中的一个分片),将查询语句转到请求节点中。
  3. 请求节点将语句转换,比如如果用到了 Distributed 表,将其转成为本地表,之后将查询语句请求到 ClickHouse 集群所有分片进行数据查询。
  4. 每个分片执行完查询语句后,返回结果给请求节点
  5. 请求节点根据用户的查询逻辑,合并最终的结果,并返回给 ClickHouse 代理
  6. 最终,ClickHouse 代理将结果返回给Client,业务层进行数据的使用

五、索引

主键稀疏索引(一级索引)

MergeTree 的主键使用 PRIMARY KEY 定义后,MergeTree 会依据 index_granularity 间隔(默认 8192 行)为数据表生成一级索引并保存至 primary.idx 文件中(索引文件包含每个索引行标记的主键值),并按照主键进行排序。

之所以叫稀疏索引,是因为它本质上是对一个完整index granularity(默认8192行)的统计信息,并不会具体记录每一行在文件中的位置。这样设计的好处是允许主键索引很小(它可以完全加载到主内存中),加快查询执行时间。

ClickHouse按照主键列的顺序将一组行存储在磁盘上。与直接定位单个行(如基于B-Tree的索引)不同,稀疏主索引允许它快速(通过对索引项进行二分查找)识别可能匹配查询的行组。然后潜在的匹配行组(颗粒)以并行的方式被加载到ClickHouse引擎中,以便找到匹配的行。

主键索引可以是组合索引, 类似于mysql的组合索引, CK在查询时也必须满足最左匹配原则, 即查询时必须从最左的字段匹配起, 一旦有跳过字段方式, 索引将无法命中。

1、只指定了排序键,主键将隐式定义为排序键。
2、为了提高内存效率,我们显式地指定了一个主键,只包含查询过滤的列。基于主键的主索引被完全加载到主内存中。
3、如果同时指定了主键和排序键,则主键必须是排序键的前缀。

跳数索引(二级索引)

意思是并非记录每个编号内的索引, 而是选择一批编号进行计算。目的与一级索引作用一样,帮助查询时减少数据扫描的范围。

跳数索引需要定义,它支持使用元组和表达式的形式声明,其完整的定义语法如下所示:
INDEX index_name expr TYPE index_type(…) GRANULARITY granularity

ALTER TABLE skip_table ADD INDEX vix my_value TYPE set(100) GRANULARITY 2;

●索引名称 用于在每个分区中创建索引文件
●索引类型 决定是否可以跳过读取和计算每个索引块
●索引表达式 用于计算存储在索引中的值集
●GRANULARITY 每个索引块由颗粒(granule)组成

索引类型:
●minmax
不需要参数。存储每个块的索引表达式的最小值和最大值,即索引文件里保存了每个granule的最大值和最小值(针对索引列),然后在查询时,根据查询条件是否在最大值和最小值框定的范围内来确定是否排除granule。

●set
接受单个参数max_size(0允许无限数量的离散值)。
在索引文件里保存每个granule的所有唯一值。优势是不存在假阳性,有就是有,没有就是没有,查询效率很高。缺点是索引的大小不可控,如果数据的唯一值比较多,索引就会变得很大,反而影响查询性能。
max_size:当一个granule的唯一值数量超过max_size时,就不保存这个granule的set。

例如set(100)设置max_size是100时,如果一个granule的唯一值超过了100,那么对这个granule就不会保存对应set,则每次查询时都会读取这个granule。

●Bloom Filter Types
一种数据结构,允许对集合成员进行高效的是否存在测试。优点是索引大小是固定的,不会因数据的分布发生变化,缺点则是假阳性问题。
1)bloom_filter 保存数据到 bloom filter 中
2)tokenbf_v1 对字符串做 tokenization 后再保存到 bloom filter 中
3)ngrambf_v1 对字符串做 ngram 后再保存到 bloom filter 中,适合字符串 LIKE 搜索

跳数索引主要的目的为判断查询的数值是否存在, 如果不存在则跳过,。
数据存储其实并非按照8192行方式存储, 而是通过一个个数据块方式存储。 如果一个编号的数据太小, 就会将合并多个编号内的数据; 如果一个编号数据又太大, 就会拆分一个编号的数据. 而.mrk文件实际上就是管理这层一对多,多对一关系以及维护存储上offset索引的数据结构.

索引并不一定能够提高查询性能,必须根据数据分布的特点谨慎地建索引。

六、优化

6.1 数据类型选择

建表时能用数值型或日期时间型表示的字段就不要用字符串,String类型为非固定长度数据类型,相对于固定长度的数据类型,处理效率更低。

6.2 分区设计

考虑到Clickhouse的底层存储结构,写入、读取、查询条件预先筛选满足条件的分区,设立合适的分区条件,减少分区的合并操作和提高使用过程中的效率

6.3 索引设计

主键用于生成稀疏索引(Granule中的起始值),排序建用于part内数据排序。所以主键序列和Order By序列要排序方式保存一致的(默认主键序列是和Order By序列保持完全一致的,或者把主键序列定义成Order By序列的部分前缀。),这样数据的一致性较高,压缩性会好。

6.4 物化视图

对于一些确定的数据模型,可以将统计指标通过物化视图的方式进行构建,这样可避免数据查询时重复计算的过程。

6.5 Join查询

当多表联查时,查询的数据仅从其中一张表出时,可考虑用 IN 操作而不是JOIN
select a.* frm a where a.uid in (select uid from b) ;

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

6.6 写入&删除

不要执行单条或小批量删除和插入操作,这样会产生小分区文件,给后台Merge任务带来巨大压力。

每次写入影响到的分片应该更小,不要一次写入太多分区,或数据写入太快,数据写入太快会导致Merge速度跟不上而报错

七、系统表&常用语句

7.1 system.clusters 当前节点关联到的集群信息

select * from system.clusters;

7.2 system.query_log 当前系统查询日志信息

SELECT * FROM system.query_log;

7.3 system.parts 当前所有表的分区信息

SELECT
sum(rows) AS 总行数,
formatReadableSize(sum(data_uncompressed_bytes)) AS 原始大小,
formatReadableSize(sum(data_compressed_bytes)) AS 压缩大小,
round((sum(data_compressed_bytes) / sum(data_uncompressed_bytes)) * 100, 0) AS 压缩率
FROM system.parts

7.4 system.mutations 当前的mutation任务

SELECT COUNT(*) FROM system.mutations WHERE table =‘member_every_day_statistics’ and is_done =0 LIMIT 1;

7.5 system.disks 当前节点磁盘信息

SELECT * FROM system.disks;

7.6 system.replicas 当前系统的同步表的相关信息

SELECT * FROM system.replicas WHERE is_readonly
OR is_session_expired
OR future_parts > 20
OR parts_to_check > 10
OR queue_size > 20
OR inserts_in_queue > 10
OR log_max_index - log_pointer > 10
OR total_replicas < 2
OR active_replicas < total_replicas;

7.7 system.processes 查看当前正在执行的查询任务

SELECT query_id,
user,
address,
query
FROM system.processes ORDER BY query_id;

八、常见问题

8.1 Memory limit (for query) exceeded:would use 9.37 GiB (attempt to allocate chunk of 301989888 bytes), maximum: 9.31 GiB**

内存使用过大(max_memory_usage),查询被强制KILL。
group by, order by , count distinct,join这样的复杂的SQL,查询的空间需要使用大量的内存。
1)如果是group by内存不够,推荐配置上max_bytes_before_external_group_by参数,当使用内存到达该阈值,进行磁盘group by
2)如果是order by内存不够,推荐配置上max_bytes_before_external_sort参数,当使用内存到达该阈值,进行磁盘order by
3)如果是count distinct内存不够,推荐使用一些预估函数(如果业务场景允许),这样不仅可以减少内存的使用同时还会提示查询速度
4)对于jon场景,需要注意Clickhousez在JOIN时会将右表进行多节点传输(右表广播)
若当前查询内存使用不高的话,确认后台分区合并是否占用掉了内存,查看system.merges/system.muations确认情况

8.2 Table readonly

1)zk 的响应超时,table 可能变成只读模式
2)zk 中的元数据信息不正确,无法完成数据同步

8.3 Too many parts(xxx). Merges are processing significantly slower than inserts**

插入语句导致本地有太多的分片目录需要合并,每一条插入语句都会根据插入语句生成对应本次写入分片目录,如果文件目录生成过快,merge跟不上就会报这个错误。
检查插入语句的频次是否过高,是否是批次批次的方式写入,并且每一个批次影响的分片数应该足够小

8.4 版本历史BUG:version(20.3.8.53) groupBitmapState 和 bitmapToArray转换 错误

官网issues

九、场景示例(略)

十、附录

Clickhouse官方中文文档
Clickhouse系列专栏
数据仓库与联机分析处理
Clickhouse压缩
ClickHouse源码分析
Clickhouse底层原理
Clickhouse索引结构设计

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值