# clickhouse

Yandex 于 2016 年开源的列式存储数据库(DBMS),使用 C++ 语言编写,主要用于在线分析处理查询(OLAP),能够使用 SQL 查询实时生成分析数据报告

列式储存的好处:

➢ 对于列的聚合,计数,求和等统计操作原因优于行式存储。

➢由于某一列的数据类型都是相同的,针对于数据存储更容易进行数据压缩,每一列选择更优的数据压缩算法,大大提高了数据的压缩比重。

➢ 由于数据压缩比更好,一方面节省了磁盘空间,另一方面对于 cache 也有了更大的发挥空间。

DBMS 的功能

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

多样化引擎

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

高吞吐写入能力

ClickHouse 采用类 LSMTree 的结构,数据写入后定期在后台 Compadction。通过类 LSM tree 的结构,ClickHouse 在数据导入时全部是顺序 append 写,写入后数据段不可更改,在后台 compaction 时也是多个段 mergesort 后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力,即便在 HDD 上也有着优异的写入性能。

数据分区与线程级并行

ClickHouse 将数据划分为多个 partition, 每个 partition 再进一步划分为多个 index granularity (索引粒度), 然后通过多个 CPU 核心分别处理其中的一部分来实现并行数据处理。在这种设计下,单条 Query 就能利用整机所有 CPU。极致的并行处理能力,极大的降低了查询延时。

所以,ClickHouse 即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端就是对于单条查询使用多 cpu, 就不利于同时并发多条直询。所以对于高 qps 的查询业务,ClickHouse 并不是强项。

# 安装单机 clickhouse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
前置准备
vim /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
* soft nproc 131072
* hard nproc 131072


/etc/security/limits.d/nproc.conf
* soft nofile 65536
* hard nofile 65536
* soft nproc 131072
* hard nproc 131072


apt install libtool *unixODBC* -y

jps -ml # 备份进程
123633 org.apache.hadoop.hdfs.server.namenode.NameNode
124131 org.apache.hadoop.yarn.server.nodemanager.NodeManager
124804 org.apache.hadoop.util.RunJar /root/apache-hive-3.1.3-bin/lib/hive-service-3.1.3.jar org.apache.hive.service.server.HiveServer2
131204 org.apache.hadoop.util.RunJar /root/apache-hive-3.1.3-bin/lib/hive-metastore-3.1.3.jar org.apache.hadoop.hive.metastore.HiveMetaStore
123831 org.apache.hadoop.hdfs.server.datanode.DataNode
152133 org.apache.flink.runtime.webmonitor.history.HistoryServer --configDir /root/flink-1.17.2/conf
191481 sun.tools.jps.Jps -ml
44508 org.apache.spark.deploy.history.HistoryServer
124335 org.apache.hadoop.mapreduce.v2.hs.JobHistoryServer

https://packages.clickhouse.com/deb/pool/main/c/
https://clickhouse.com/docs/en/install#available-installation-options

# 执行后返回
ClickHouse has been successfully installed.

Start clickhouse-server with:
sudo clickhouse start/status/restart

Start clickhouse-client with:
clickhouse-client --password

# clickhouse备份文件,默认在/etc/clickhouse-server
config.xml 服务端配置
users.xml 参数配置


# 修改配置文件
vim config.xml
<listen_host>::</listen_host>


# 执行查询操作
clickhouse-client --query ""
clickhouse-client -m




# 数据类型

整形

Uint 8/16/32/64

int 8/16/32/64

浮点型

Float32/64

布尔型

可以用 uint8,取值限制 0 1

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

字符串

String

FixedString (N) 固定长度,不够添加空字节,超过返回错误

枚举类型

Enum8/16 用 String = intx 描述

时间类型

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

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

Datetime64 接受年 - 月 - 日 时:分: 秒。亚秒的字符串比如 '20199-12-16 20:50:10.66

数组

Array (T) T 类型数组

Array (1,2) [2,1] 一个数组中类型必须相同

不能在 mergetree 表中使用多维数组

Nullable

引擎分为表引擎和库引擎

# 表引擎

表引擎决定了如何存储数据,大小写敏感

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

# TinyLog

以列文件的形式保存在磁盘上,不支持索引,没有并发控制。一般保存少量数据的小表

生产环境上作用有限。可以用于平时练习测试用。

create table haha(id String, name String) ENGINE=TinyLog;

# Memory

内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的的性能表现 (超过 10G/s)。

一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大 (上限大概 1 亿行) 的场景。

# MergeTree

ClickHouse 中最强大的表引擎当属 MergeTree (合并树) 引擎及该系列 (*MergeTree) 中的其他引擎,支持索引和分区,地位可以相当于 innodb 之于 Mysql。而且基于 MergeTree, 还衍生出了,也是非常有特色的引擎。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 建表显示指定表引擎
create table t_sn (id Uint32, id String,amount Decimal(16,2))engine=MergeTree partition by toYYYYMMDD(create_time) primary key(id) order by(id);

# primary key
主键可以重复。提供数据一级索引,但不是唯一约束

# order by是必须的字段
# primary key,partition by不是必须的字段


# partition by
分区目录
降低扫描范围,优化查询速度
MergeTree是以列文件+索引文件+表定义文件组成的,但是如果定了分区那么这些文件就会保存到不同的分区目录中。如果不指定,只会使用一个分区
分区后,面对涉及跨分区的查询统计,ClickHouse 会以分区为单位并行处理。

数据写入与分区合并
任何一个批次的数据写入都会产生一个临时分区,不会纳入任何一个已有的分区。写入 后的某个时刻(大概 10-15 分钟后),ClickHouse 会自动执行合并操作(等不及也可以手动 通过 optimize 执行),把临时分区的数据,合并到已有分区中。
optimize table xxxx final;
# clickhouse 文件结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);

insert into t_order_mt values
(101,'sku_001',1000.00,'2020-06-01 12:00:00') , (102,'sku_002',2000.00,'2020-06-01 11:00:00'), (102,'sku_004',2500.00,'2020-06-01 12:00:00'), (102,'sku_002',2000.00,'2020-06-01 13:00:00'), (102,'sku_002',12000.00,'2020-06-01 13:00:00'), (102,'sku_002',600.00,'2020-06-02 12:00:00');

root@hadoop100:/var/lib/clickhouse/data/default/t_order_mt# ll
drwxr-x--- 2 clickhouse clickhouse 4096 Aug 20 18:19 20200601_1_1_0/
drwxr-x--- 2 clickhouse clickhouse 4096 Aug 20 18:19 20200601_1_1_1/
drwxr-x--- 2 clickhouse clickhouse 4096 Aug 20 18:19 20200602_2_2_0/
drwxr-x--- 2 clickhouse clickhouse 4096 Aug 20 18:19 20200602_2_2_1/
drwxr-x--- 2 clickhouse clickhouse 4096 Aug 20 18:18 detached/
-rw-r----- 1 clickhouse clickhouse 1 Aug 20 18:18 format_version.txt

root@hadoop100:/var/lib/clickhouse/data/default/t_order_mt/20200602_2_2_1# ll
-rw-r----- 1 clickhouse clickhouse 333 Aug 20 18:19 checksums.txt
-rw-r----- 1 clickhouse clickhouse 118 Aug 20 18:19 columns.txt
-rw-r----- 1 clickhouse clickhouse 1 Aug 20 18:19 count.txt
-rw-r----- 1 clickhouse clickhouse 128 Aug 20 18:19 data.bin
-rw-r----- 1 clickhouse clickhouse 66 Aug 20 18:19 data.cmrk3
-rw-r----- 1 clickhouse clickhouse 10 Aug 20 18:19 default_compression_codec.txt
-rw-r----- 1 clickhouse clickhouse 1 Aug 20 18:19 metadata_version.txt
-rw-r----- 1 clickhouse clickhouse 8 Aug 20 18:19 minmax_create_time.idx
-rw-r----- 1 clickhouse clickhouse 4 Aug 20 18:19 partition.dat
-rw-r----- 1 clickhouse clickhouse 42 Aug 20 18:19 primary.cidx
-rw-r----- 1 clickhouse clickhouse 292 Aug 20 18:19 serialization.json

bin 文件:数据文件

mrk 文件:标记文件

标记文件在 idx 索引文件和 bin 数据文件之间起到了桥梁作用

以 mrk2 结尾的文件,表示该表启用了自适应索引间隔。

primary.idx 文件:主键索引文件,用于加快查询效率

minmax_create_time.idx; 分区键的最大最小值

checksums.txt: 校验文件,用于校验各个文件的正确性。右屏放各个文件的 size 以及 hash 值。

20200602_2_2_1

PartitionId MinBlockNum MaxBlockNum_Level

分区值最小 分区块编号最大 分区块编号合并层级

# primary key

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

主键的设定主要依据是查询语句中的 where 条件。 根据条件通过对主键进行某种形式的二分查找,能够定位到对应的 index granularity, 避

免了全表扫描。

index granularity: 索引粒度,指在稀疏索引中两个相邻索引对应数据的间隔。ClickHouse 中的 MergeTree 默认是 8192。官方不建议修改这个值,除非该列存在大量重复值,比如在一个分区中几万行才有一个不同数据

稀疏索引

image-20250226013615511

稀疏索引的好处就是可以用很少的索引数据,定位更多的数据,代价就是只能定位到索 引粒度的第一行,然后再进行进行一点扫描。

# order by

order by 设定了分区内的数据按照哪些字段顺序进行有序保存,MergeTree 中唯一必选项。因为当用户不 设置主键的情况,很多处理会依照 order by 的字段进行处理

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

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

# 二级索引
1
2
3
4
5
6
7
8
9
10
create table t_order_mt2(  
id UInt32,
sku_id String,
total_amount Decimal(16,2),
create_time Datetime,
INDEX a total_amount TYPE minmax GRANULARITY 5
) engine =MergeTree
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id, sku_id);

index a total_amount TYPE minmax GRAANULARITY 5

GRANULARITY N 是设定二级索引对于一级索引粒度的粒度。

a 别名

total_amount 字段名

minmax 索引类型

GRABULARIRT 索引粒度

二级索引能够为非主键字段的查询发挥作用

# TTL

TTL 即 Time To Live,MergeTree 提供了可以管理数据表或者列的生命周期的功能。

列级别 ttl

1
2
3
4
5
6
7
8
9
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

1
alter table t_order_mt3 MODIFY TTL create_time + INTERVAL 10 SECOND;

涉及判断的字段必须是 Date 或者 Datetime 类型,推荐使用分区的日期字段。

能够使用的时间周期:

  • SECOND
  • MINUTE
  • HOUR
  • DAY
  • WEEK
  • MONTH
  • QUARTER
  • YEAR
# TTL 执行行为

Type of TTL rule may follow each TTL expression. It affects an action which is to be done once the expression is satisfied (reaches current time):

  • DELETE delete expired rows (default action);
  • RECOMPRESS codec_name recompress data part with the codec_name ;
  • TO DISK 'aaa' move part to the disk aaa ;
  • TO VOLUME 'bbb' move part to the disk bbb ;
  • GROUP BY aggregate expired rows.

# ReplacingMergeTree

ReplacingMergeTree 是 MergeTree 的一个变种,它存储特性完全继承 MergeTree,只是多了一个去重的功能。 尽管 MergeTree 可以设置主键,但是 primary key 其实没有唯一约束的功能。

1)去重时机

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

2)去重范围

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

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

1
2
3
4
5
6
7
8
9
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 () 填入的参数为版本字段,重复数据保留版本字段值最大的。

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

通过测试得到结论

➢ 实际上是使用 order by 字段作为唯一键

➢ 去重不能跨分区

➢ 只有同一批插入(新版本)或合并分区时才会进行去重

➢ 认定重复的数据保留,版本字段值最大的

➢ 如果版本字段相同则按插入顺序保留最后一条

# SummingMergeTree

对于不查询明细,只关心以维度进行汇总聚合结果的场景。如果只使用普通的 MergeTree 的话,无论是存储空间的开销,还是查询时临时聚合的开销都比较大。

ClickHouse 为了这种场景,提供了一种能够 “预聚合” 的引擎 SummingMergeTree

(1)创建表

1
2
3
4
5
6
7
8
9
create table t_order_smt(
id UInt32,
sku_id String,
total_amount Decimal(16,2) ,
create_time Datetime
) engine =SummingMergeTree(total_amount)
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id,sku_id );

根据 order by 进行预聚合,聚合时保留最早的一条

➢ 以 SummingMergeTree()中指定的列作为汇总数据列

➢ 可以填写多列必须数字列,如果不填,以所有非维度列且为数字列的字段为汇总数据列

➢ 以 order by 的列为准,作为维度列

➢ 其他的列按插入顺序保留第一行

➢ 不在一个分区的数据不会被聚合

➢ 只有在同一批次插入 (新版本) 或分片合并时才会进行聚合

如果要是获取汇总值,还是需要使用 sum 进行聚合,这样效率会有一定的提高,但本身 ClickHouse 是列式存储的,效率提升有限,不会特别明显。

1
select sum(total_amount) from province_name=’’ and create_date=‘xxx’

# SQL 操作

# insert

1
2
3
4
(1)标准 
insert into [table_name] values(…),(….)
(2)从表到表的插入
insert into [table_name] select a,b,c from [table_name_2]

# update & delete

ClickHouse 提供了 Delete 和 Update 的能力,这类操作被称为 Mutation 查询,它可以看做 Alter 的一种。 虽然可以实现修改和删除,但是和一般的 OLTP 数据库不一样,Mutation 语句是一种很 “重” 的操作,而且不支持事务

“重” 的原因主要是 ** 每次修改或者删除都会导致放弃目标数据的原有分区,重建新分区。** 所以尽量做批量的变更,不要进行频繁小数据的操作。

1
2
3
4
(1)删除操作 
alter table t_order_smt delete where sku_id ='sku_001';
(2)修改操作
alter table t_order_smt update total_amount=toDecimal32(2000.00,2) where id =102;

Mutation 语句分两步执行,同步执行的部分其实只是进行 新增数据新增分区和并把旧分区打上逻辑上的失效标记。直到触发分区合并的时候,才会删 除旧数据释放磁盘空间

更新:

插入一条新的数据,_version + 1

查询的时候加上一个过滤条件 where version 最大

删除:

sign,0 表示未删除,1 表示已删除

查询的时候加上一个过滤条件,where_sign=0 and version = 最大

# 查询

  • 支持子查询
  • 支持 CTE (Common Table Expression 公用表表达式 with 子句)
  • 支持各种 JOIN, 但是 JOIN 操作无法使用缓存,所以即使是两次相同的 JOIN 语句,ClickHouse 也会视为两条新 SQL
  • 窗口函数 https://clickhouse.com/docs/en/sql-reference/window-functions
  • 不支持自定义函数
  • GROUP BY 操作增加了 with rollup\with cube\with total 用来计算小计和总计。

with rollup/with cube / with total

1
select a, b from t_order_mt group by x  with rollup;

纬度是 a,b

rollup: 上卷

group by a

group by a,b

group by

cube: 多维分析

group by a

group by b

group by a,b

group by

total: 总计

group by a,b

group by

# alert

1)新增字段

alter table tableName add column newcolname String after col1;

2)修改字段类型

alter table tableName modify column newcolname String;

3)删除字段

alter table tableName drop column newcolname;

# 导出

1
clickhouse-client --query "select * from t_order_mt where  create_time='2020-06-01 12:00:00'" --format CSVWithNames>  /opt/module/data/rs1.csv

# 副本

image-20250226013630641

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 配置zk
vim /etc/clickhouse-server/config.d/metrika.xml
<?xml version="1.0"?>
<yandex>
<zookeeper-servers>
<node index="1">
<host>hadoop102</host>
<port>2181</port>
</node>
<node index="2">
<host>hadoop103</host>
<port>2181</port>
</node>
<node index="3">
<host>hadoop104</host>
<port>2181</port>
</node>
</zookeeper-servers>
</yandex>

chown clickhouse:clickhouse /etc/clickhouse-server/config.d/metrika.xml

vim config.xml
<zookeeper incl="zookeeper-servers" optional="true" />
<include_from>/etc/clickhouse-server/config.d/metrika.xml</include_from>

clickhouse restart

副本只能同步数据,不能同步表结构。需要手工建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Hadoop100
create table t_order_rep2 (
id UInt32,
sku_id String,
total_amount Decimal(16,2),
create_time Datetime
) engine =ReplicatedMergeTree('/clickhouse/table/01/t_order_rep','rep_100')
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id,sku_id);


Hadoop101:
create table t_order_rep2 (
id UInt32,
sku_id String,
total_amount Decimal(16,2),
create_time Datetime
) engine =ReplicatedMergeTree('/clickhouse/table/01/t_order_rep','rep_101')
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id,sku_id);


第一个参数是分片的 zk_path 一般按照:/clickhouse/table/{shard}/{table_name} 的格式,如果只有一个分片写01即可
第二个参数数副本名称,相同的分片副本名称不能相同。

# 分片集群

副本虽然能够提高数据的可用性,降低丢失风险,但是每台服务器实际上必须容纳全量数据,对数据的横向扩容没有解决。

要解决数据水平切分的问题,需要引入分片的概念。通过分片把一份完整的数据进行切分,不同的分片分布到不同的节点上,再通过 Distributed 表引擎把数据拼接起来一同使用。

Distributed 表引擎本身不存储数据,有点类似于 MyCat 之于 MySql,成为一种中间件, 通过分布式逻辑表来写入、分发、路由来操作多台节点不同分片的分布式数据。

image-20250226013643694

一般开启 internal_replication ,即使用分片同步副本数据。

ClickHouse 的集群是表级别的,实际企业中,大部分做了高可用,但是没有用分片,避免降低查询性能以及操作集群的复杂性。

image-20250226013652131

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
vim metrika-shard.xml
<yandex>
<remote_servers>
<gmall_cluster>
<!-- 集群名称-->
<shard>
<!--集群的第一个分片-->
<internal_replication>true</internal_replication>
<!--该分片的第一个副本-->
<replica>
<host>hadoop101</host>
<port>9000</port>
</replica>
<!--该分片的第二个副本-->
<replica>
<host>hadoop102</host>
<port>9000</port>
</replica>
</shard>
<shard>
<!--集群的第二个分片-->
<internal_replication>true</internal_replication>
<replica>
<!--该分片的第一个副本-->
<host>hadoop103</host>
<port>9000</port>
</replica>
<replica>
<!--该分片的第二个副本-->
<host>hadoop104</host>
<port>9000</port>
</replica>
</shard>
<shard>
<!--集群的第三个分片-->
<internal_replication>true</internal_replication>
<replica>
<!--该分片的第一个副本-->
<host>hadoop105</host>
<port>9000</port>
</replica>
<replica>
<!--该分片的第二个副本-->
<host>hadoop106</host>
<port>9000</port>
</replica>
</shard>
</gmall_cluster>
</remote_servers>
</yandex>

<macros>
<shard>01</shard> <!--不同机器放的分片数不一样-->
<replica>rep_1_1</replica> <!--不同机器放的副本数不一样-->
</macros>



<include_from> /xxxx/metrika-shard.xml
# 建表
create table st_order_mt on cluster gmall_cluster (
id UInt32,
sku_id String,
total_amount Decimal(16,2),
create_time Datetime
) engine=ReplicatedMergeTree('/clickhouse/tables/{shard}/st_order_mt','{replica}')
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id,sku_id);

# 创建分布式表
create table st_order_mt_all2 on cluster gmall_cluster
(
sku_id String,
total_amount Decimal(16,2),
create_time Datetime
)engine = Distributed(gmall_cluster,default, st_order_mt,hiveHash(sku_id));

Distributed(集群名称,库名,本地表名,分片键)
分片键必须是整型数字,所以用 hiveHash 函数转换,也可以 rand()

# 往分布式表插入和查询数据
insert into st_order_mt_all2 values (201,'sku_001',1000.00,'2020-06-01 12:00:00') , (202,'sku_002',2000.00,'2020-06-01 12:00:00'), (203,'sku_004',2500.00,'2020-06-01 12:00:00'), (204,'sku_002',2000.00,'2020-06-01 12:00:00'), (205,'sku_003',600.00,'2020-06-02 12:00:00');

# explain

查看 sql 语句执行计划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
EXPLAIN [AST | SYNTAX | PLAN | PIPELINE] [setting = value, ...] SELECT ... [FORMAT ...]
PLAN:用于查看执行计划,默认值。
AST :用于查看语法树;
SYNTAX:用于优化语法;
PIPELINE:用于查看 PIPELINE 计划
EXPLAIN plan select arrayJoin([1,2,3,null,null]);

EXPLAIN
SELECT arrayJoin([1, 2, 3, NULL, NULL])

Query id: 4d263f99-155f-4158-b611-115a64acc2eb

┌─explain─────────────────────────────────────────────────────────────────────────────────┐
1. │ Expression ((Project names + (Projection + Change column names to column identifiers))) │
2. │ ReadFromStorage (SystemOne) │
└─────────────────────────────────────────────────────────────────────────────────────────┘

2 rows in set. Elapsed: 0.001 sec.




explain select database,table,count(1) cnt from system.parts where database in ('datasets','system') group by database,table order by
database,cnt desc limit 2 by database;

EXPLAIN
SELECT
database,
`table`,
count(1) AS cnt
FROM system.parts
WHERE database IN ('datasets', 'system')
GROUP BY
database,
`table`
ORDER BY
database ASC,
cnt DESC
LIMIT 2 BY database

Query id: 4f3d43d3-4f79-4748-a774-4cf6fd44adce

┌─explain────────────────────────────────────────────────────────────────────┐
1. │ Expression (Project names) │
2. │ LimitBy │
3. │ Expression (Before LIMIT BY) │
4. │ Sorting (Sorting for ORDER BY) │
5. │ Expression ((Before ORDER BY + Projection)) │
6. │ Aggregating │
7. │ Expression (Before GROUP BY) │
8. │ Filter ((WHERE + Change column names to column identifiers)) │
9. │ ReadFromSystemPartsBase │
└────────────────────────────────────────────────────────────────────────────┘

9 rows in set. Elapsed: 0.002 sec.



EXPLAIN AST SELECT number from system.numbers limit 10;

EXPLAIN AST
SELECT number
FROM system.numbers
LIMIT 10

Query id: ef9768e7-41ab-437d-9707-2e85703e46ef

┌─explain─────────────────────────────────────┐
1. │ SelectWithUnionQuery (children 1) │
2. │ ExpressionList (children 1) │
3. │ SelectQuery (children 3) │
4. │ ExpressionList (children 1) │
5. │ Identifier number │
6. │ TablesInSelectQuery (children 1) │
7. │ TablesInSelectQueryElement (children 1) │
8. │ TableExpression (children 1) │
9. │ TableIdentifier system.numbers │
10.Literal UInt64_10 │
└─────────────────────────────────────────────┘

10 rows in set. Elapsed: 0.001 sec.



打开全部参数
EXPLAIN header=1, actions=1,description=1 SELECT number from system.numbers limit 10;




//开启三元运算符优化
SET optimize_if_chain_to_multiif = 1;
//再次查看语法优化
EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'atguigu') FROM numbers(10);




EXPLAIN PIPELINE
SELECT
database,
`table`,
count(1) AS cnt
FROM system.parts
WHERE database IN ('datasets', 'system')
GROUP BY
database,
`table`
ORDER BY
database ASC,
cnt DESC
LIMIT 2 BY database

Query id: 58b9bb5b-0abc-47de-8322-c1af458e3e54

┌─explain───────────────────────────────────────────┐
1. │ (Expression) │
2. │ ExpressionTransform │
3. │ (LimitBy) │
4. │ LimitByTransform │
5. │ (Expression) │
6. │ ExpressionTransform │
7. │ (Sorting) │
8. │ MergingSortedTransform 41
9. │ MergeSortingTransform × 4
10. │ LimitsCheckingTransform × 4
11. │ PartialSortingTransform × 4
12. │ (Expression) │
13. │ ExpressionTransform × 4
14. │ (Aggregating) │
15. │ Resize 14
16. │ AggregatingTransform │
17. │ (Expression) │
18. │ ExpressionTransform │
19. │ (Filter) │
20. │ FilterTransform │
21. │ (ReadFromSystemPartsBase) │
└───────────────────────────────────────────────────┘

# 建表优化

# 数据类型

建表时能用数值型或日期时间型表示的字段就不要用字符串,全 String 类型在以 Hive 为中心的数仓建设中常见,但 ClickHouse 环境不应受此影响。 虽然 ClickHouse 底层将 DateTime 存储为时间戳 Long 类型,但不建议存储 Long 类型, 因为 DateTime 不需要经过函数转换处理,执行效率高、可读性好。

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

存在 null 的时候会多存一列 null.bin

null 无法被索引,效率

# 分区和索引

分区粒度根据业务特点决定,不宜过粗或过细。一般选择按天分区,也可以指定为 Tuple (), 以单表一亿数据为例,分区大小控制在 10-30 个为最佳。

必须指定索引列,ClickHouse 中的索引列即排序列,通过 order by 指定,一般在查询条件中经常被用来充当筛选条件的属性被纳入进来;可以是单一维度,也可以是组合维度的索 引;通常需要满足高级列在前、查询频率大的在前原则;还有基数特别大的不适合做索引列, 如用户表的 userid 字段;通常筛选后的数据满足在百万以内为最佳。

# 表参数

Index_granularity 是用来控制索引粒度的,默认是 8192,如非必须不建议调整。

如果表中不是必须保留全量历史数据,建议指定 TTL(生存时间值),可以免去手动过期历史数据的麻烦,TTL 也可以通过 alter table 语句随时修改。

# 写入和删除优化

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

(2)不要一次写入太多分区,或数据写入太快,数据写入太快会导致 Merge 速度跟不上而报错,一般建议每秒钟发起 2-3 次写入操作,每次操作写入 2w~5w 条数据(依服务器 性能而定)

1
2
3
4
5
“ Too many parts 处理 ” :使用 WAL 预写日志,提高写入性能。 
in_memory_parts_enable_wal 默认为 true

在服务器内存充裕的情况下增加内存配额,一般通过 max_memory_usage 来实现
在服务器内存不充裕的情况下,建议将超出部分内容分配到系统硬盘上,但会降低执行 速度,一般通过max_bytes_before_external_group_by、max_bytes_before_external_sort 参数来实现。

# 配置优化

配置项主要在 config.xml 或 users.xml 中, 基本上都在 users.xml 里

config.xml 的配置项

https://clickhouse.tech/docs/en/operations/server-configuration-parameters/settings/

users.xml 的配置项

https://clickhouse.tech/docs/en/operations/settings/settings/

# 语法优化

ClickHouse 的 SQL 优化规则是基于 RBO (Rule Based Optimization)

# count 优化

在调用 count 函数时,如果使用的是 count () 或者 count (*),且没有 where 条件,则 会直接使用 system.tables 的 total_rows

1
2
3
EXPLAIN SELECT count()FROM datasets.hits_v1; 

Union Expression (Projection) Expression (Before ORDER BY and SELECT) MergingAggregated ReadNothing (Optimized trivial count)

Optimized trivial count ,这是对 count 的优化。 如果 count 具体的列字段,则不会使用此项优化:

# 消除子查询重复字段

子查询中有两个重复的 id 字段,会被去重

1
2
3
EXPLAIN SYNTAX SELECT  a.UserID,  b.VisitID,  a.URL,  b.UserID  FROM  hits_v1 AS a  LEFT JOIN (  SELECT  UserID,  UserID as HaHa,  VisitID  FROM visits_v1) AS b  USING (UserID)  limit 3; 

//返回优化语句: SELECT UserID, VisitID, URL, b.UserID FROM hits_v1 AS a ALL LEFT JOIN ( SELECT UserID, VisitID FROM visits_v1 ) AS b USING (UserID) LIMIT 3

# 谓词下推

当 group by 有 having 子句,但是没有 with cube、with rollup 或者 with totals 修饰的时 候,having 过滤会下推到 where 提前过滤。例如下面的查询,HAVING name 变成了 WHERE name,在 group by 之前过滤:

1
2
3
4
5
6
7
8
9
EXPLAIN SYNTAX SELECT UserID FROM hits_v1 GROUP BY UserID HAVING UserID = '8585742290196126178'; 

//返回优化语句 SELECT UserID FROM hits_v1 WHERE UserID = \'8585742290196126178\' GROUP BY UserID
EXPLAIN SYNTAX SELECT * FROM ( SELECT UserID FROM visits_v1 ) WHERE UserID = '8585742290196126178'


//返回优化后的语句

SELECT UserID FROM ( SELECT UserID FROM visits_v1 WHERE UserID = \'8585742290196126178\' ) WHERE UserID = \'8585742290196126178\'

# 聚合计算外推

1
2
3
4
5
6
7
聚合函数内的计算,会外推,例如: 

EXPLAIN SYNTAX SELECT sum(UserID * 2) FROM visits_v1


//返回优化后的语句
SELECT sum(UserID) * 2 FROM visits_v1

# 聚合函数消除

1
2
3
4
如果对聚合键,也就是 group by key 使用 minmaxany 聚合函数,则将函数消除, 例如: 
EXPLAIN SYNTAX SELECT sum(UserID * 2), max(VisitID), max(UserID) FROM visits_v1 GROUP BY UserID

SELECT sum(UserID) * 2, max(VisitID), UserID FROM visits_v1 GROUP BY UserID

# 删除重复 order by key

1
2
3
4
5
EXPLAIN SYNTAX SELECT * FROM visits_v1 ORDER BY  UserID ASC,  UserID ASC,  VisitID ASC, VisitID ASC 

//返回优化后的语句:

select …… FROM visits_v1 ORDER BY UserID ASC, VisitID ASC

# 删除重复 limit by key

1
2
3
EXPLAIN SYNTAX SELECT * FROM visits_v1 LIMIT 3 BY  VisitID,  VisitID LIMIT 10 
//返回优化后的语句:
select …… FROM visits_v1 LIMIT 3 BY VisitID LIMIT 10

# 删除重复 using key

1
2
3
4
5
EXPLAIN SYNTAX SELECT  a.UserID,  a.UserID,  b.VisitID,  a.URL,  b.UserID  FROM hits_v1 AS a LEFT JOIN visits_v1 AS b USING (UserID, UserID) 

//返回优化后的语句:

SELECT UserID, UserID, VisitID, URL, b.UserID FROM hits_v1 AS a ALL LEFT JOIN visits_v1 AS b USING (UserID)

# 标量替换

1
2
3
4
5
6
7
8
9
如果子查询只返回一行数据,在被引用的时候用标量替换,例如下面语句中的 total_disk_usage 字段: 

EXPLAIN SYNTAX WITH ( SELECT sum(bytes) FROM system.parts WHERE active ) AS total_disk_usage SELECT (sum(bytes) / total_disk_usage) * 100 AS table_disk_usage, table FROM system.parts GROUP BY table ORDER BY table_disk_usage DESC LIMIT 10;


//返回优化后的语句:


WITH CAST(0, \'UInt64\') AS total_disk_usage SELECT (sum(bytes) / total_disk_usage) * 100 AS table_disk_usage, table FROM system.parts GROUP BY table ORDER BY table_disk_usage DESC LIMIT 10

# 三元运算符优化

1
2
3
4
5
6
7
8
9
如果开启了 optimize_if_chain_to_multiif 参数,三元运算符会被替换成 multiIf 函数, 例如: 

EXPLAIN SYNTAX SELECT number = 1 ? 'hello' : (number = 2 ? 'world' : 'atguigu') FROM numbers(10) settings optimize_if_chain_to_multiif = 1;


//返回优化后的语句:


SELECT multiIf(number = 1, \'hello\', number = 2, \'world\', \'atguigu\') FROM numbers(10) SETTINGS optimize_if_chain_to_multiif = 1

# 单表查询优化

# prewhere 替代 where

where 过滤行,选出列

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

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

1
2
# 关闭where自动优化为prewhere
set optimize_move_to_prewhere=0;

默认情况,我们肯定不会关闭 where 自动优化成 prewhere,但是某些场景即使开启优 化,也不会自动转换成 prewhere,需要手动指定 prewhere:

  • 使用常量表达式
  • 使用默认值为 alias 类型的字段
  • 包含了 arrayJOIN,globalIn,globalNotIn 或者 indexHint 的查询
  • select 查询的列字段和 where 的谓词相同
  • 使用了主键字段

# 数据采样

通过采样运算可极大提升数据分析的性能

SELECT Title,count (*) AS PageViews FROM hits_v1 SAMPLE 0.1 WHERE CounterID =57 GROUP BY Title ORDER BY PageViews DESC LIMIT 1000 #代表采样 10% 的数据,也可以是具体的条数

采样修饰符只有在 MergeTree engine 表中才有效,且在创建表时需要指定采样策略。

# 列裁剪、分区裁剪

数据量太大时应避免使用 select * 操作,查询的性能会与查询的字段大小和数量成线性 表换,字段越少,消耗的 io 资源越少,性能就会越高。

1
2
3
分区裁剪就是只读取需要的分区,在过滤条件中指定。 

select WatchID, JavaEnable, Title, GoodEvent, EventTime, EventDate, CounterID, ClientIP, ClientIP6, RegionID, UserID from datasets.hits_v1 where EventDate='2014-03-23';

# order by 结合 limit,where

1
2
3
千万以上数据集进行 order by 查询时需要搭配 where 条件和 limit 语句一起使用。 
#正例:
SELECT UserID,Age FROM hits_v1 WHERE CounterID=57 ORDER BY Age DESC LIMIT 1000

# 避免构建虚拟列

1
2
3
4
5
6
7
如非必须,不要在结果集上构建虚拟列,虚拟列非常消耗资源浪费性能,可以考虑在前 端进行处理,或者在表中构造实际字段进行额外存储。

SELECT Income,Age,Income/Age as IncRate FROM datasets.hits_v1;

正例:拿到 Income 和 Age 后,考虑在前端进行处理,或者在表中构造实际字段进行额外存储

SELECT Income,Age FROM datasets.hits_v1;

# uniqcombined 替代 distinct

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

反例: select count(distinct rand()) from hits_v1;
正例: SELECT uniqCombined(rand()) from datasets.hits_v1

使用物化视图

# 其他注意事项

(1)查询熔断 为了避免因个别慢查询引起的服务雪崩的问题,除了可以为单个查询设置超时以外,还 可以配置周期熔断,在一个查询周期内,如果用户频繁进行慢查询操作超出规定阈值后将无 法继续进行查询操作。

(2)关闭虚拟内存 物理内存和虚拟内存的数据交换,会导致查询变慢,资源允许的情况下关闭虚拟内存。

(3)配置 join_use_nulls 为每一个账户添加 join_use_nulls 配置,左表中的一条记录在右表中不存在,右表的相应字段会返回该字段相应数据类型的默认值,而不是标准 SQL 中的 Null 值。

(4)批量写入时先排序 批量写入数据时,必须控制每个批次的数据中涉及到的分区的数量,在写入之前最好对 需要导入的数据进行排序。无序的数据或者涉及的分区太多,会导致 ClickHouse 无法及时对 新导入的数据进行合并,从而影响查询性能。

(5)关注 CPU cpu 一般在 50% 左右会出现查询波动,达到 70% 会出现大范围的查询超时,cpu 是最关 键的指标,要非常关注。

# 多表查询优化

A join B B 加载到内存,A 的每条数据去内存查

# 用 in 代替 join

1
2
3
当多表联查时,查询的数据仅从其中一张表出时,可考虑用 IN 操作而不是 JOIN 

insert into hits_v2 select a.* from hits_v1 a where a. CounterID in (select CounterID from visits_v1);

# 大小表 join

小表在右,右表加载入内存

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

1)小表在右
insert into table hits_v2 select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. CounterID;

# 谓词下推

1
2
3
4
5
6
ClickHouse 在 join 查询时不会主动发起谓词下推的操作,需要每个子查询提前完成过滤操作,需要注意的是,是否执行谓词下推,对性能影响差别很大(新版本中已经不存在此问 题,但是需要注意谓词的位置的不同依然有性能的差异)


insert into hits_v2 select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. CounterID where a.EventDate = '2014-03-17';

insert into hits_v2 select a.* from ( select * from hits_v1 where EventDate = '2014-03-17' ) a left join visits_v2 b on a. CounterID=b. CounterID;

# 分布式表使用 global

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

# 使用字典表

将一些需要关联分析的业务创建成字典表进行 join 操作,前提是字典表不宜太大,因为字典表会常驻内存

# 提前过滤

通过增加逻辑过滤可以减少数据扫描,达到提高执行速度及降低内存消耗的目的

ck 的 join:

1、原理:

右表加载到内存,再去匹配

2、为什么 join 不行:因为 1

3、非要使用,怎么用比较好:

能过滤先过滤,特别是右表

右边放小表

特殊场景可以考虑使用字典表

可以替换的话,尽量不要用 join, 比如用 in 实现

# 数据一致性

我们在使用 ReplacingMergeTree、SummingMergeTree 这类表引擎的时候,会出现短暂 数据不一致的情况。

# 手动 OPTIMIZE

在写入数据后,立刻执行 OPTIMIZE 强制触发新写入分区的合并动作。

OPTIMIZE TABLE test_a FINAL;

# 通过 Group by 去重

1
2
3
4
5
6
7
执行去重的查询 

SELECT user_id , argMax(score, create_time) AS score, argMax(deleted, create_time) AS deleted, max(create_time) AS ctime FROM test_a GROUP BY user_id HAVING deleted = 0;

函数说明: argMax(field1,field2):按照 field2 的最大值取 field1 的值。 当我们更新数据时,会写入一行新的数据,例如上面语句中,通过查询最大的 create_time 得到修改后的 score 字段值。

加标记字段实现

# final 查询去重

在查询语句后增加 FINAL 修饰符,这样在查询的过程中将会执行 Merge 的特殊逻辑(例 如数据去重,预聚合等)。

20.5 之后版本,final 是多线程,但是读取分区是串行的。

# 物化视图

普通视图不保存数据,保存的仅仅是查询语句,查询的时候还是从原表读取数据,可以将普通视图理解为是个子查询。物化视图则是把查询的结果根据相应的引擎存入到了磁盘或内存中,对数据重新进行了组织,你可以理解物化视图是完全的一张新表。ClickHouse 的物化视图是一种查询结果的持久化

优点:查询速度快,要是把物化视图这些规则全部写好,它比原数据查询快了很多,总的行数少了,因为都预计算好了。

缺点:它的本质是一个流式数据的使用场景,是累加式的技术,所以要用历史数据做去重、去核这样的分析,在物化视图里面是不太好用的。在某些场景的使用也是有限的。而且 如果一张表加了好多物化视图,在写这张表的时候,就会消耗很多机器的资源,比如数据带 宽占满、存储一下子增加了很多。

也是 create 语法,会创建一个隐藏的目标表来保存视图数据。也可以 TO 表名,保存到 一张显式的表。没有加 TO 表名,表名默认就是 .inner. 物化视图名

1
2
3
4
5
6
7
8
9
10
 CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]table_name [TO[db.]name] [ENGINE = engine] [POPULATE] AS SELECT ...
物化视图限制
1.必须指定物化视图的 engine 用于数据存储
2.TO [db].[table]语法的时候,不得使用 POPULATE。
3.查询语句(select)可以包含下面的子句: DISTINCT, GROUP BY, ORDER BY, LIMIT…
4.物化视图的 alter 操作有些限制,操作起来不大方便。
5.若物化视图的定义使用了 TO [db.]name 子语句,则可以将目标表的视图 卸载 DETACH 再装载 ATTACH

clickhouse 官方并不推荐使用 POPULATE,因为在创建物化视图的过程中同时写入 的数据不能被插入物化视图。
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'

如果需要历史数据,手动 insert 到 view 里

1
2
3
4
5
6
7
8
9
10
#导入增量数据 
INSERT INTO hits_test SELECT EventDate, CounterID, UserID, URL, Income FROM hits_v1 WHERE EventDate >= '2014-03-23' limit 10;

#查询物化视图

SELECT * FROM hits_mv;


# 查询物化视图
`.innner.hits_mv`

# MaterializeMysql

ClickHouse 20.8.2.3 版本新增加了 MaterializeMySQL 的 database 引擎,该 database 能 映 射 到 MySQL 中 的 某 个 database , 并 自 动 在 ClickHouse 中 创 建 对 应 的 ReplacingMergeTree。ClickHouse 服务做为 MySQL 副本,读取 Binlog 并执行 DDL 和 DML 请 求,实现了基于 MySQL Binlog 机制的业务数据库实时同步功能。

(1)MaterializeMySQL 同时支持全量和增量同步,在 database 创建之初会全量同步 MySQL 中的表和数据,之后则会通过 binlog 进行增量同步。

(2)MaterializeMySQL database 为其所创建的每张 ReplacingMergeTree 自动增加了 _sign 和 _version 字段。 其中,_version 用作 ReplacingMergeTree 的 ver 版本参数,每当监听到 insert、update 和 delete 事件时,在 databse 内全局自增。而 _sign 则用于标记是否被删除,取值 1 或 者 -1。

1
2
3
4
5
6
7
8
9
1)确保 MySQL 开启了 binlog 功能,且格式为 ROW 打开/etc/my.cnf,在[mysqld]下添加:
server-id=1
log-bin=mysql-bin
binlog_format=ROW

2)开启 GTID 模式 如果如果 clickhouse 使用的是 20.8 prestable 之后发布的版本,那么 MySQL 还需要配置 开启 GTID 模式, 这种方式在 mysql 主从模式下可以确保数据同步的一致性(主从切换时)。
gtid-mode=on
enforce-gtid-consistency=1 # 设置为主从强一致性
log-slave-updates=1 # 记录日志

# 监控 clickhouse

ClickHouse 运行时会将一些个自身的运行状态记录到众多系统表中 (system.*)。所以我们对于 CH 自身的一些运行指标的监控数据,也主要来自这些系统表

ClickHouse 从 v20.1.2.4 开始,内置了对接 Prometheus 的功能,配置的方式也很简单,可以将其作为 Prometheus 的 Endpoint 服务,从而自动的将 metrics 、 events 和 asynchronous_metrics 三张系统的表的数据发送给 Prometheus。

安装 Prometheus,修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"

# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.

static_configs:
- targets: ["localhost:9090"]

- job_name: "clickhouse"
static_configs:
- targets: ["hadoop100:9363"]

启动 Prometheus 和 grafana

修改 clickhouse 配置文件 config.xml,重启服务

1
2
3
4
5
6
7
8
<prometheus>
<endpoint>/metrics</endpoint>
<port>9363</port>

<metrics>true</metrics>
<events>true</events>
<asynchronous_metrics>true</asynchronous_metrics>
</prometheus>

访问 web:http://x.x.x.x:9363/metrics 确认开启

配置 grafana

配置 datasource

添加 dashboard

# 数据备份

https://clickhouse.tech/docs/en/operations/backup/

# 手动备份

ClickHouse 允许使用 ALTER TABLE … FREEZE PARTITION … 查询创建表分区的本地副本。 这是利用硬链接 (hardlink) 到 /var/lib/clickhouse/shadow/ 文件夹中实现的,所以它通常不会因为旧数据而占用额外的磁盘空间。 创建的文件副本不由 ClickHouse 服务器处理,所以不需要任何额外的外部系统就有一个简单的备份。防止硬件问题,最好将它们远程复制到另一个位置,然后删除本地副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
5.1.1 创建备份路径 
(1)创建用于存放备份数据的目录 shadow
sudo mkdir -p /var/lib/clickhouse/shadow/
如果目录存在,先清空目录下的数据
5.1.2 执行备份命令
echo -n 'alter table t_order_mt freeze' | clickhouse-client
5.1.3 将备份数据保存到其他路径
#创建备份存储路径
sudo mkdir -p /var/lib/clickhouse/backup/
#拷贝数据到备份路径
sudo cp -r /var/lib/clickhouse/shadow/
/var/lib/clickhouse/backup/my-backup-name
#为下次备份准备,删除 shadow 下的数据
sudo rm -rf /var/lib/clickhouse/shadow/*
5.1.4 恢复数据
(1)模拟删除备份过的表
echo ' drop table t_order_mt ' | clickhouse-client
(2)重新创建表
cat events.sql | clickhouse-client
(3)将备份复制到 detached 目录
sudo cp -rl
backup/my-backup-name/1/store/cb1/cb176503-cd88-4ea8-8b17-6503cd888ea8/* data/default/t_order_mt/detached/
ClickHouse 使用文件系统硬链接来实现即时备份,而不会导致 ClickHouse 服务停机(或锁定)。这些硬链接可以进一步用于有效的备份存储。在支持硬链接的文件系统(例如本地文件系统或 NFS)上,将 cp 与-l 标志一起使用(或将 rsync 与–hard-links 和–numeric-ids 标志一起使用)以避免复制数据。

注意:仅拷贝分区目录,注意目录所属的用户要是 clickhouse
(4)执行 attach
echo 'alter table t_order_mt attach partition 20200601' | clickhouse-client
(5)查看数据
echo 'select count() from t_order_mt' | clickhouse-client

# clickhouse-backup

我们可以使用 Clickhouse 的备份工具 clickhouse-backup 帮我们自动化实现

https://github.com/AlexAkulov/clickhouse-backup/

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

John Doe WeChat Pay

WeChat Pay

John Doe Alipay

Alipay

John Doe PayPal

PayPal