第一章.ClickHouse介绍
1.特点
ClickHouse是俄罗斯的Yandex于2016年开源的列式存储数据库(DBMS),使用C++语言编写,主要用于在线分析处理查询(OLAP),能够使用SQL查询实时生成分析数据报告
- OLAP场景的关键特征
1.大多数是读请求
2.数据总是以相当大的批(>1000 rows)进行写入
3.不修改已添加的数据
4.每次查询都从数据库中读取大量的行,但是同时又仅需要少量的列
5.宽表,即每个表包含着大量的列
6.较少的查询(通常每台服务器每秒数百个查询或更少)
7.对于简单查询,允许延迟大约50ms
8.列中的数据相对较小:数字和短字符串,例如,(每个URL60个字节)
9.处理单个查询时需要高吞吐量
10.事务不是必须的
11.对数据一致性要求低
12.每一个查询除了一个大表外都很小
13.查询结果明显小于源数据,换句话说,数据被过滤或聚合后能够被盛放在单台服务器的内存中
clickHouse的特点
一.采用列式存储
好处:
1.对于列的聚合,计数,求和等统计操作优于行式存储
2.由于某一列的数据类型都是相同的,针对于数据存储更容易进行数据压缩,每一列选择更优的数据压缩算法,大大提高了数据的压缩比重
3.由于数据压缩更好,一方面节省了磁盘空间,另一方面对于cache也有了更大的发挥空间
二.DBMS功能
1.ClickHouse支持基于SQL的声明式查询语言,该语言大部分情况下是与SQL标准兼容的
2.支持的查询包括GROUP BY,ORDER BY, IN,JOIN以及非相关子查询
3.不支持窗口函数和相关子查询
三.多样化引擎
1.ClickHouse和mysql类似,把表级的存储引擎插件化,根据表的不同需求可以设定不同的存储引擎
2.目前包括合并树,日志,接口和其他四大类20多种引擎
四.高吞吐写入能力
1.ClickHouse采用类LSM Tree的结构,数据写入后不定期在后台Compaction
2.通过类LSM tree的结构,ClickHouse在数据导入时全部是顺序append写的,写入后数据段不可更改, 在后台Compaction时也是多个段merge sort后顺序写回磁盘
3.顺序写的特性,充分利用了磁盘的吞吐能力,即便在HDD(机械硬盘)上也有着优异的写入性能
4.官方公布 benchmark测试显示能够达到50MB-200MB/s的写入吞吐能力,按照每行100Byte估算,大约相当于50W~200W条/s的写入速度
五.数据分区和线程并行
1.ClickHouse将数据划分为多个partition,每个partition再进一步划分为多个index granularity(粒度),然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理
2.在这种设计下,单条查询就能利用整机所有CPU,极致的并行处理能力,极大降低了查询延迟
3.所以clickhouse即使对于大量数据的查询也能够化整为0平行处理
4.但是有一个弊端就是对于单条查询使用多CPU,就不利于同时并发多条查询,所以对于高qps的查询业 务,clickhouse并不是强项
六.一些限制
1.没有完整的事务支持
2.缺少高频率,低延迟的修改或删除已存在数据的能力,仅能用于批量删除或修改数据,但这符合GDPR
3.稀疏索引使得clickhouse不适合通过其键检索单行的点查询
2.安装单机版ClickHouse
- 准备工作
一.Centos取消打开文件数限制
sudo vim /etc/security/limits.conf
#添加以下内容, 如果已经添加过, 则修改
* soft nofile 65536
* hard nofile 65536
* soft nproc 131072
* hard nproc 131072
二.Centos取消SELINUX
sudo vim /etc/sysconfig/selinux
SELINUX=disabled
三.关闭防火墙,如果已经关闭,跳过该步骤
sudo firewall-cmd --state #查看防火墙状态
sudo systemctl stop firewalld #关闭防火墙
sudo systemctl disable firewalld #关闭开机自启动(因为防火墙是服务,所以开机会自启,需要关闭)
- 单机安装
一.安装依赖工具
sudo yum -y install yum-utils initscripts
二.准备离线安装包
clickhouse-client-20.6.4.44-2.noarch.rpm clickhouse-common-static-20.6.4.44-2.x86_64.rpm clickhouse-server-20.6.4.44-2.noarch.rpm
三.使用rpm安装
sudo rpm -ivh clickhouse-common-static-20.6.4.44-2.x86_64.rpm
sudo rpm -ivh clickhouse-client-20.6.4.44-2.noarch.rpm
sudo rpm -ivh clickhouse-server-20.6.4.44-2.noarch.rpm
四.修改配置文件
sudo vim /etc/clickhouse-server/config.xml
# 打开注释
<listen_host>::</listen_host> #来自任何地方的客户端连接当前服务器(IPv4和ipv6)都可以
# 在docker容器中由于不支持ipv6, 所以这个配置会导致服务器起不来, 应该改成只支持ipv4就行了(0.0.0.0)
五.启动ClickServer
sudo systemctl start clickhouse-server #启动clickhouse服务
clickhouse-client -h hadoop162 -m #连接clickhouse客户端
第二章.数据类型
官方文档: https://clickhouse.com/docs/zh/sql-reference/data-types/
1.整数类型
2.浮点型
3.Decimal类型
4.布尔类型
5.字符串
6.枚举类型
7.数组
第三章.表引擎
表引擎是clickhouse的一大特色,可以说表引擎决定了如何存储数据,包括
- 数据的存储方式和位置,写到哪里以及从哪里读取数据
- 支持哪些查询以及如何支持
- 并发数据访问
- 索引的作用(如果存在)
- 是否可以执行多线程请求
- 数据复制参数
1.如何使用表引擎
表引擎的使用方式就是必须显示在创建表时定义该表使用的引擎,以及引擎使用的相关参数
如
create table t_tinylog ( id String, name String) engine=TinyLog;
注意:引擎的名称大小写敏感
2.常用引擎
- TinyLog
以列文件的形式保存在磁盘上,不支持索引,没有并发控制,一般保存少量数据的小表,生产环境上作用有限,可以用于平时练习测试用
- Memory
内存引擎,数据以未压缩的原始形式直接保存在内存里,服务器重启数据就会丢失,读写操作数据不会阻塞,不支持索引,简单查询下有非常非常高的性能表现(超过10G/s)
一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大(上限大概1亿行)的场景
create table t_memory(id Int16, name String) engine=Memory;
insert into t_memory values(1, 'lisi');
- 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)
#MergeTree其实还有很多参数(绝大多数用默认值即可),但是上面3个标红参数是更加重要的,也涉及了关于MergeTree的很多概念。
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')
3.Partition分区
作用:学过hive的应该都不陌生,分区的目的主要是减少扫描的范围,优化查询速度。如果不填: 只会使用一个分区
分区目录:MergeTree是以列文件加索引文件加表定义文件组成的,但是如果设定了分区那么这些文件就会保存在不同的分区目录中
并行:分区后,面对涉及跨分区的查询统计,clickhouse会以分区为单位并行处理
数据写入与分区合并:任何一个批次的数据写入都会产生一个临时分区,不会纳入一个已有的分区,写入后的某个时刻(大概10-15分钟后),clickhouse会自动执行合并操作(等不及也可以手动通过optimize执行),把临时分区的数据,合并到已有分区中(optimize table xxxx [final])
4.Primary key主键
1. clickhouse中的主键,和其他数据库不太一样,它只提供的数据的一级索引,但是却不是唯一约束,这就意味着是可以存在相同primary key的数据的
2. 主键的设定主要依据是查询语句中的where条件,根据条件通过对主键进行某种形式的二分查找,能够定位到对应的index granularity避免了全包扫描
3. index granularity:
直接翻译的话就是索引粒度,指在稀疏索引中两个相邻索引对应数据的间隔。clickhouse中的MergeTree默认是8192。官方不建议修改这个值,除非该列存在大量重复值,比如在一个分区中几万行才有一个不同数据。
4. 稀疏索引:稀疏索引的好处就是可以用很少的索引数据,定位更多的数据,代价就是只能定位到索引粒度的第一行,然后再进行一点一点的扫描,由于稀疏索引比较少,所以理论上可以完全加载到内存中,从而提高查询速度
5.order by
order by设定了分区内的数据按照哪些字段顺序进行有序保存
order by是MergeTree中唯一一个必填项,甚至比primary key还重要,因为当用户不设置主键的情况,很多处理会依照order by的字段进行处理(比如后面讲的去重和汇总)
要求:主键必须是order by字段的前缀字段,比如order by的字段是(id,sku_id),那么主键必须是id或者(id,sku_id)
6.数据TTL
TTL 即Time To Live,mergeTree提供了可以管理数据或者列的生命周期的功能
# 列级别的TTL 针对某列数据过期
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)
insert into t_order_mt3
values(106,'sku_001',1000.00,'2021-01-16 10:58:30') ,
(107,'sku_002',2000.00,'2020-06-12 22:52:30'),
(110,'sku_003',600.00,'2021-01-17 12:00:00')
# 表级别的TTL 针对整张表数据过期
alter table t_order_mt3 MODIFY TTL create_time + INTERVAL 20 SECOND;
#判断时间 涉及判断的字段必须是Date或者DateTime类型,推荐使用分区的日期字段
#能够使用的时间周期
- SECOND
- MINUTE
- HOUR
- DAY
- WEEK
- MONTH
- QUARTER
- YEAR
7.ReplacingMergeTree
ReplacingMergeTree是MergeTree的一个变种,它存储特性完全继承MergeTree,只是多了一个去重的功能。
尽管MergeTree可以设置主键,但是primary key其实没有唯一约束的功能。如果你想处理掉重复的数据,可以借助这个ReplacingMergeTree
什么样的数据是重复? order by字段相同认为重复
去重时机:数据的去重只会在合并的过程中实现,合并会在未知的时间在后台进行,所以你无法预先做出计划,有一些数据可能仍未被处理,即使使用optimize也不能保证一定会去重
去重范围:如果表经过了分区,去重只会在分区内部进行去重,不能执行跨分区的去重
所以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);
# 插入数据
insert into t_order_rmt
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')
# 执行合并
optimize table t_order_rmt final;
该引擎使用场景:ReplacingMergeTree 适用于在后台清除重复的数据以节省空间,但是它不保证没有重复的数据出现。
8.SummingMergeTree
对于不查询明细,只关心以维度进行汇总聚合结果的场景。如果只使用普通的MergeTree的话,无论是存储空间的开销,还是查询时临时聚合的开销都比较大。
Clickhouse 为了这种场景,提供了一种能够“预聚合”的引擎: SummingMergeTree.
# 建表
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 )
# 插入数据
insert into t_order_smt
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')
# optimize:会自动进行预聚合
结论:
1. 以SummingMergeTree(字段)中指定的列作为汇总数据列。可以填写多列必须数字列,如果不填,以所有非维度列, 非主键且为数字列的字段为汇总数据列\
2. 以order by 的列为准,作为维度列(group by …)
3. 其他的列保留第一行。
4. 不在一个分区的数据不会被预聚合。
5. 聚合发生的时机不确定
6. 查询的时候仍然需要sql聚合语句
第四章.SQL操作
1.insert
insert values
INSERT INTO [db.]table [(c1, c2, c3)] VALUES (v11, v12, v13), (v21, v22, v23), ...
insert select
INSERT INTO [db.]table [(c1, c2, c3)] SELECT ...
2.update和delete
clickhouse提供了Delete和Update的能力,这类操作被称为Mutation查询,它可以看做alter的一种
虽然可以实现修改和删除,但是和一般的OLTP数据可不一样,Mutation语句是一种很重的操作,而且不支持事务
"重"的原因主要是每次修改或者删除都会导致放弃目标数据的原有分区,重建新分区,所以尽量做批量的变更,不要进行频繁小数据的操作
由于操作比较“重”,所以 Mutation语句分两步执行,同步执行的部分其实只是进行新增数据新增分区和并把旧分区打上逻辑上的失效标记。直到触发分区合并的时候,才会删除旧数据释放磁盘空间。
# 删除操作
alter table t_order_smt delete where sku_id ='sku_001';
# 修改操作
alter table t_order_smt
update total_amount=toDecimal32(2000.00,2)
where id =102;
3.查询操作
支持如下操作
WITH clause
FROM clause
SAMPLE clause
JOIN clause
PREWHERE clause
WHERE clause
GROUP BY clause
LIMIT BY clause
HAVING clause
SELECT clause
DISTINCT clause
LIMIT clause
UNION ALL clause
INTO OUTFILE clause
FORMAT clause
不支持窗口函数
不支持自定义函数
group by子句特殊说明
GROUP BY 操作增加了 with rollup\with cube\with totals 用来计算小计和总计。
1. 插入数据
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 12:00:00'),
(103,'sku_004',2500.00,'2020-06-01 12:00:00'),
(104,'sku_002',2000.00,'2020-06-01 12:00:00')
(105,'sku_003',600.00,'2020-06-02 12:00:00'),
(106,'sku_001',1000.00,'2020-06-04 12:00:00'),
(107,'sku_002',2000.00,'2020-06-04 12:00:00'),
(108,'sku_004',2500.00,'2020-06-04 12:00:00'),
(109,'sku_002',2000.00,'2020-06-04 12:00:00'),
(110,'sku_003',600.00,'2020-06-01 12:00:00')
2. with rollup:从右至左去掉维度进行小计
select id , sku_id,sum(total_amount) from t_order_mt group by id,sku_id with rollup;
3. with cube:各种维度组合进行聚合
select id , sku_id,sum(total_amount) from t_order_mt group by id,sku_id with cube;
4. with taotals:仅仅多了一个总计
select id , sku_id,sum(total_amount) from t_order_mt group by id,sku_id with totals;
4.alter操作
同mysql的修改字段基本一致
# 新增子段
alter table tableName add column newcolname String after col1;
# 修改字段类型
alter table tableName modify column newcolname String;
# 删除字段
alter table tableName drop column newcolname;
5.导出数据
clickhouse-client --query "select toHour(create_time) hr ,count(*) from test1.order_wide where dt='2020-06-23' group by hr" --format CSVWithNames> ~/rs1.csv
支持的数据格式: https://clickhouse.tech/docs/v19.14/en/interfaces/formats/
第五章.分片集群(高并发)
为了解决数据水平切分的问题,需要引入切片的概念
通过分片把一份完整的数据进行切分,不同的分片分布在不同的节点上,再通过Distributed表引擎把数据拼接起来一同使用
Distributed表引擎本身不存储数据,有点类似于MyCat之于Mysql,成为一种中间件,通过分布式逻辑表来写入,分发,路由来操作多台节点不同分片的分布式数据
1.读写原理
2.分片集群规划
hadoop162 | hadoop163 | hadoop164 |
---|---|---|
distribute | ||
shard1 replical | shard1 replical | shard2 replical |
3.配置分片集群
hadoop162
- 配置metrika.xml
sudo vim /etc/clickhouse-server/config.d/metrika.xml
<?xml version="1.0"?>
<yandex>
<clickhouse_remote_servers>
<gmall_cluster> <!-- 集群名称-->
<shard> <!--集群的第一个分片-->
<internal_replication>true</internal_replication>
<replica> <!-- 该分片的第一个副本 -->
<host>hadoop162</host>
<port>9000</port>
</replica>
<replica> <!-- 该分片的第二个副本-->
<host>hadoop163</host>
<port>9000</port>
</replica>
</shard>
<shard> <!--集群的第二个分片-->
<internal_replication>true</internal_replication>
<replica> <!-- 该分片的第一个副本-->
<host>hadoop164</host>
<port>9000</port>
</replica>
</shard>
</gmall_cluster>
</clickhouse_remote_servers>
<zookeeper-servers>
<node index="1">
<host>hadoop162</host>
<port>2181</port>
</node>
<node index="2">
<host>hadoop163</host>
<port>2181</port>
</node>
<node index="3">
<host>hadoop164</host>
<port>2181</port>
</node>
</zookeeper-servers>
<!-- 宏: 将来建表的时候, 可以从这里自动读取, 每个机器上的建表语句就可以一样了 相当于变量 -->
<macros>
<shard>01</shard> <!-- 不同机器放的分片索引不一样, hadoop163,hadoop164需要更改 -->
<replica>hadoop162</replica> <!-- 不同机器放的副本数不一样, hadoop163,hadoop164需要更改, 以主机命名比较方便-->
</macros>
- 配置config.xml
sudo vim /etc/clickhouse-server/config.xml
#找到zookeeper节点,在下面添加如下内容
<include_from>/etc/clickhouse-server/config.d/metrika.xml</include_from>
hadoop163和hadoop164只用修改宏即可
- hadoop163的宏
<!-- 宏: 将来建表的时候, 可以从这里自动读取, 每个机器上的建表语句就可以一样了 相当于变量 -->
<macros>
<shard>01</shard> <!-- 不同机器放的分片索引不一样, hadoop163,hadoop164需要更改 -->
<replica>hadoop163</replica> <!-- 不同机器放的副本数不一样, hadoop163,hadoop164需要更改, 以主机命名比较方便-->
</macros>
</yandex>
- hadoop164的宏
<!-- 宏: 将来建表的时候, 可以从这里自动读取, 每个机器上的建表语句就可以一样了 相当于变量 -->
<macros>
<shard>02</shard> <!-- 不同机器放的分片索引不一样, hadoop163,hadoop164需要更改 -->
<replica>hadoop164</replica> <!-- 不同机器放的副本数不一样, hadoop163,hadoop164需要更改, 以主机命名比较方便-->
</macros>
</yandex>
- 3个节点创建数据库
分别在hadoop162,hadoop163,hadoop164上创建数据库gmall
create database gmall;
use gmall;
- 在hadoop162上创建本地表
在hadoop162上创建本地表,会自动同步到其他节点
本地表只负责存储自己的切片数据
create table st_order_mt_gmall on cluster gmall_cluster (
id UInt32,
sku_id String,
total_amount Decimal(16,2),
create_time Datetime
) engine =ReplicatedMergeTree('/clickhouse/tables/gmall/{shard}/st_order_mt_gmall','{replica}')
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id,sku_id);
- 在hadoop162上创建分布式表
create table st_order_mt_gmall_all on cluster gmall_cluster
(
id UInt32,
sku_id String,
total_amount Decimal(16,2),
create_time Datetime
)engine = Distributed(gmall_cluster,gmall, st_order_mt_gmall,hiveHash(sku_id));
- 通过分布式表添加数据
insert into st_order_mt_gmall_all
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');
- 查询数据
通过分布式表查询数据,可以查询到所有数据
通过本地表查询,只能查询到当前节点的分片数据