ClickHouse 是 俄罗斯的 Yandex 公司于2016年开源的 列式存储数据库(DBMS),使用 C++语言 编写,主要用于在线分析处理查询(OLAP),能够使用 SQL 查询实时生成分析数据报告。
支持标准 SQL 的大部分语法,包括 DDL 数据定义 和 DML 数据操纵,以及相关函数,用户管理以及权限管理,数据备份与恢复
和 MySQL 类似,把表级的存储引擎插件化,根据表的不同需求可设置不同的存储引擎,目前有合并树、日志、接口和其他四大类的二十多种引擎。
ClickHouse 采用类 日志结构合并树 LSM Tree(Log-Structured-Merge-Tree),数据写入后,定期在后台进行压缩(Compaction)
通过类 LSM Tree 的存储结构,ClickHouse 写数据是以追加(append)的方式,且写入后数据段不可更改。
在后台执行压缩(Compaction)时是多个段 归并(Merge)、排序(Sort)后按顺序写回磁盘。
顺序写的特性:有效利用磁盘的吞吐能力
ClickHouse 将数据划分为多个 partition(分区),每个 partition 再划分为多个 index granularity(索引粒度),然后通过多个 CPU 核心分别处理其中的一部分,从而实现并行的数据处理
特点:单条 Query 查询语句就能利用整机的所有 CPU,并行处理降低了查询的延时。
不过这种并行方式使得 不便同时并发多条查询,,ClickHouse 并不擅长处理高qps( 每秒查询率 = 并发数/平均响应时间)的查询业务
ClickHouse 可以在任何具有 x86_64、AArch64 或 PowerPC64LE CPU 架构的 Linux、FreeBSD 或 Mac OS X 上运行。
官方文档 快速入门:https://clickhouse.com/docs/en/quick-start/
ClickHouse官网:Fast Open-Source OLAP DBMS - ClickHouse
下载地址:https://packages.clickhouse.com/rpm/stable/
添加官方的库:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://packages.clickhouse.com/rpm/clickhouse.repo
下载
sudo yum install -y clickhouse-server clickhouse-client
我这里的 Docker 是本机虚拟机里配置的 Docker
# 启动 docker
sudo systemctl start docker
# 创建容器
sudo docker run --name c1 -d -it -p 8181:8181 clickhouse/clickhouse-server
查看容器 IP
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' c1
设置宿主机到虚拟机里Docker容器的端口映射,保证能ping通,这里可参考:
https://blog.csdn.net/Unirithe/article/details/120711045
使用 Docker 的话就无需配置和启动了,只要容器在运行,ClickHouse 服务就默认启动了,比如访问:http://172.17.0.2:8123/,会有一个 Ok.的结果
Click House 官方默认是关闭外部连接的,需手动配置
sudo vim /etc/clickhouse-server/config.xml
# 在 vim 查询 ::
/<listen_host>::</listen_host>
把
除了打开
查看 clickhouse-server 的安装目录
rpm -ql clickhouse-server.x86_64
启动 clickhouse
sudo /etc/init.d/clickhouse-server start
在之前的依赖包中,我们同时也安装了 Click House 的客户端,接下来使用客户端的命令连接启动的服务端,查看一下是否能正常连接,如果是通过方式一安装的 clickhouse(都安装在Linux),则执行
clickhouse-client -m
如果是通过 Docker 安装的,这里我采用的是 Docker放 Click House 服务端,而 Docker宿主机就放 Click Client 客户端进行连接,则执行下面命令,指定容器的IP即可:
clickhouse-client -h 172.17.0.2 -m
测试命令,查询所有的数据库:
:) show databases
方式一安装下测试成功的结果如下图:
官方文档:https://clickhouse.com/docs/en/sql-reference/data-types/
固定长度,分为有符号和无符号
有符号整型
取值范围:[ − 2 n -2^n −2n ~ 2 n − 1 − 1 2^{n-1} - 1 2n−1−1]
类型 | 数据范围 |
---|---|
Int8 | -128 ~ 127 |
Int16 | -32768 ~ 32767 |
Int32 | -2147483648 ~ 2147483647 |
int64 | -9223372036854775808~9223372036854775807 |
无符号整型
取值范围:[ 0 0 0~ 2 n − 1 2^n - 1 2n−1]
类型 | 数据范围 |
---|---|
UInt8 | 0~255 |
UInt16 | 0~65535 |
UInt32 | 0 ~ 4294967295 |
Uint64 | 0 ~18446744073709551615 |
使用场景:记录个数、数量、存储型 id 等
浮点型有 Float32 和 Float64 两种,类似 Java 里的 float 和 double
建议:尽量用整数存储数据,例,将固定精度的数字转为整数值,如时间用毫秒为单位表示,因为浮点型计算可能会出现误差,例如在 ClickHouse中计算 1.0 - 0.9 ,结果是 0.0999… 趋近于0.1, 而不是 0.1
使用场景:数据值较小,不涉及大量统计计算,且精度要求不高时,如保存商品的重量。
Click House 没有提供 bool 类型 ,一般可使用 UInt8 类型来表示, 取值限制为 0 或1 即可
Decimal 为有符号的浮点数,在加减乘运算中保持精度,而在触发中,最低有效数字会被舍去。
声明:
(其中的 s 表示小数位)
使用场景:金额字段、汇率、利率等字段,为保证小数点精度,都使用 Decimal 存储
Click House 中的字符串分为两种,String 和 FixedString(N)
字符串可任意长,包含任意的字节集,包括空的字节
固定长度 N 的字符串,N 必须是严格的正整数。
类似于 MySQL里的 varchar
使用场景:名称、文字描述、字符型编码,该类型不常用
枚举类型分为 Enum8 和 Enum16 这两种,Enum枚举存储的是 ‘string’ = integer 的对应关系。 Enum8 用 ‘String’ =Int8 对描述,Enum16用‘String’ = Int16 对描述
使用示例:
CREATE TABLE t_enum(
x Enum8('hello' = 1, 'world' = 2)
) ENGINE = TinyLog;
这个 x 列 只能存储类型定义中取出的值,‘hello’ 或 ‘world’
INSERT INTO t_enum VALUES ('hello'), ('world'), ('hello');
select * from t_enum;
运行结果:
INSERT INTO t_enum VALUES ('uni');
select cast(x, 'Int8') from t_enum;
运行结果:
使用场景:对表示状态、类型的字段是一种空间优化与数据约束,数据几乎不发生变换时可以使用。
目前 ClickHouse 支持三种时间类型,分别是 Date32、Datetime 和 Datetime64
日期类型,用两个字节存储,表示从 1970-01-01 (无符号)到当前的日期值
Array(T):由 T 类型元素组成的数组
T 可以是任意类型,包含数组类型(支持嵌套)。但不推荐使用多维数组,Click House 对多维数组的支持有限,例:不能在 MergeTree 表中存储多维数组
使用示例:
select array(1, 2) as x, toTypeName(x);
运行结果:
select [1, 2] as x, toTypeName(x);
运行结果:
表引擎是 ClickHouse 的特点,它决定了如何存储表的数据,除此之外还有特点:
表引擎的使用方式就是必须显式地在创建表时定义该表使用的引擎,以及引擎相关的参数。
注:Click House的引擎名称大小写敏感
TinyLog 引擎支持以列文件的形式保存在磁盘上,但是不支持索引,且没有并发控制。一般保存少量数据的小表,适合平时测试,不适合生产环境,如:
create table t_tinylog(id String, name String) engine = TinyLog;
Memory 引擎属于内存引擎,数据以 未压缩 的原始形式直接保存在内存,只要服务器存储,数据就会消失。读写操作不会相互阻塞,不支持索引。
简单查询的性能很高(超过 10G/s)
平时运用得不多,同样是适合测试,还有需要高性能的场合。
官方参考文档:https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree/
Merge Tree 归并树引擎是 ClickHouse 中 **最强大 **的表引擎之一,包括其拓展的其他 MegeTree,如下图:
该类引擎都支持索引和分区,地位相当于 MySQL 里的 innodb 引擎。
【应用案例】
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');
select * from t_order_mt;
运行结果:
partition by 分区的作用是:降低扫描的范围,优化查询速度
分区的特点:
optimize table xxx final;
【应用案例】基于 Merge Tree 引擎中应用案例的表,接下来再插入一些数据,插入后立马查询
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');
select * from t_order_mt;
运行结果:
根据结果可以看出,当前这些数据没有纳入分区,结果中的一个表格相当于一个分区,接下来手动进行 optimize 操作,然后再次查询:
optimize table t_order_mt final;
select * from t_order_mt;
运行结果:
可以看到在执行 optimize 后之前的临时分区现在已经合并进去了,之前创建表时设置的分区字段是create_time。
ClickHouse 的主键,和其他数据库不太一样,只提供了数据的一级索引,但是却不是唯一约束,即 ClickHouse 数据表里可存在相同的 primary key 的数据
对于 ClickHouse 数据表主键的设定,其主要依据是查询语句中的 where 条件,根据条件通过对主键进行某种形式的二分查找,能够定位到对应的 index granularity(索引的粒度),避免全表扫描。
index granularity 索引粒度是指在** sparse index(稀疏索引) **中两个相邻索引对应数据的间隔。
Click House 中 MergeTree 索引粒度默认是 8192 。除非列存在大量重复值,比如几万行才有一个不同数据,不然不建议修改,参考官方介绍 :https://clickhouse.com/docs/zh/engines/table-engines/mergetree-family/mergetree
稀疏索引(图片转自 https://baijiahao.baidu.com/s?id=1694065350596413101&wfr=spider&for=pc):
稀疏索引的好处是可以用较少的索引数据来定位更多的数据,不过只能定位到索引粒度的第一行,然后在进行顺序扫描。
在创建表中指定 MergeTree 引擎时,order by 是必须要设置的,它设定了分区内的数据按照哪个/ 哪些字段顺序进行有序保存。
order by 是 Merge Tree 中唯一的一个必填项,甚至比 primary key 还重要,因为当用户不设置主键时,很多处理会按照 order by 的字段进行处理。
注: primary key 主键 必须是 order by 字段的前缀或者相同
如:order by 字段是 (id, sku_id),那么主键必须是 id 或 (id, sku_id)
【应用案例】
创建支持二级索引的数据表
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);
其中 GRANULARITY 是设定二级索引对于一级索引粒度的粒度
插入数据
insert into t_order_mt2 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');
ClickHouse 支持使用 clickhouse-client 启动客户端的命令来连接服务端并执行 sql 语句,例如:
clickhouse-client --send_logs_level=trace <<< 'select
* from t_order_mt2 where total_amount > toDecimal32(900., 2)';
运行结果:
TTL 即 Time To Live,MergeTree 提供了可以管理数据表或者列的 **生命周期 **的功能。
1)列级别 TTL
比如在创建表时给某一列设置 TTL,这里给 total_amount 字段设置 TTL create_time+interval 10 SECOND
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,now()),
(107,'sku_002',2000.00,now()),
(110,'sku_003',600.00,now());
select * from t_order_mt3;
过十秒后再次查询,total_amount 列的值全变成了0
2)表级别 TTL
以下面这条语句为例,设置表在 create_time 之后 10秒丢失
alter table t_order_mt3 modify ttl create_time interval 10 secend;
相关字段必须是 Date 或 DateTime类型,推荐使用分区的日期字段
能使用的时间周期有:
ReplacingMergeTree 是 MergeTree的一个升级版,其存储特性完全继承 MergeTree,不过多了个去重功能,由于在 MergeTree中可以设置主键,但主键没有唯一约束的功能,所以当想处理掉重复的数据时,可以使用 ReplacingMergeTree引擎
1)去重机制
数据去重只会在合并的过程中出现。合并会做未知的时间在后台进行,所以无法预先做出计划。因为有些数据可能仍然没有被处理
2)去重范围
若表经过了分区,去重只会在 **分区内部 **进行去重,不能执行跨分区的去重。
故 ReplacingMergeTree 使用于在后台清除重复的数据以节省空间,但不能保证一定没有重复的数据出现。
【应用案例】 创建根据 create_time 字段为版本号的 RMT 引擎表
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');
注:ReplacingMergeTree括号内填入的参数为版本字段,当出现重复数据时,会保留最大的版本字段的所在行,若不填,则默认按照插入顺序保留最后一条,即支持覆盖。
第一次查询数据:
select * from t_order_rmt;
手动合并后再次查询
OPTIMIZE TABLE t_order_rmt FINAL;
select * from t_order_rmt;
结论:
对于不查询明细,只关心维度进行汇总聚合结果都场景。若只使用普通的 MergeTree,无论是存储空间的开销,还是查询时临时聚合的开销,都会比较大
ClickHouse为了这种场景,提供了一种能“预聚合”的引擎 SummingMergeTree
【应用案例】创建 使用 SummingMergeTreee 引擎的表
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');
select * from t_order_smt;
合并后再次查询
OPTIMIZE TABLE t_order_smt FINAL;
select * from t_order_smt;
结论:
开发建议
设计聚合表,唯一键值、流水号可以去掉,所有字段全部是维度、度量或时间戳
ClickHouse 支持传统关系型数据库(比如 MySQL)的 SQL 语句,这里记录的是与 SQL 不一致的部分。
与标准的 SQL 基本一致
语法:
insert into [table_name] values(...), (...)
往一张表插入多条数据insert into [table_name] select a, b, c from [table_name_2]
将一张表的查询结果插入到另一张表ClickHouse 将 Update 和 Delete 操作归类为 Mutation 查询,可视为 alter 的一种。
虽然可以实现修改和删除,但 Mutation 语句不支持事务,而且每次修改或删除都会导致放弃目标数据的原有分区,重建新的分区。所以尽量做批量的变更,而不进行频繁小数据的操作。
删除语法:alter table [table_name] delete where [...]
修改语法:alter table [table_name] [...] where [...]
Mutation 语句分两步执行,分别是新增分区和标记旧分区、删除旧数据和释放磁盘空间。
ClickHouse 查询语句和 SQL 类似
【应用案例】
准备数据表和数据
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);
alter table t_order_mt delete where 1=1;
insert into t_order_mt values
(101,'sku_001',1000.00,'2020-06-01 12:00:00'),
(101,'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');
**with rollup **从右往左去掉维度进行小计
select id , sku_id,sum(total_amount) from t_order_mt group by
id,sku_id with rollup;
**with cube **先从右往左去掉维度进行小计,再从左往右去掉维度进行小计
with totals:只计算合计
select id , sku_id,sum(total_amount) from t_order_mt group by
id,sku_id with totals;
和 MySQL 修改字段的操作基本一致
语法:
alter table [tableName] add column [newColname] [colType] after [oldCol]
添加某一字段(包括类型)到指定列的后面alter table [oldTableName] modify column [newColname] [colType]
修改某一字段的名称以及类型alter table tableName drop column [oldColName]
删除字段参考:https://clickhouse.com/docs/zh/interfaces/formats
示例:
clickhouse-client --query "select * from t_order_mt" --format CSV > ./data.csv
官方文档:https://clickhouse.com/docs/zh/engines/table-engines/mergetree-family/replication
副本存在的目的是保障数据的高可用性,在集群环境下,即使一台 ClickHouse节点宕机,那么也可以从其他服务器获得相同的数据。
ClickHouse 只有 使用 MergeTree 引擎的表支持副本
副本写入流程
本篇文章记录了 ClickHouse 的基本概念、特点、数据类型、常见引擎,基本上是参考官方文档和视频资料的,通过这种方式了解了ClickHouse数据库的强大功能。下次将先对其应用进行熟悉,以单机环境为主,先熟悉单机情况下对数据库的操作。
[1] ClickHouse 官方文档
[2] 尚硅谷视频教程