ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的用于在线分析处理查询(OLAP :Online Analytical Processing)MPP架构的列式存储数据库(DBMS:Database Management System),能够使用 SQL 查询实时生成分析数据报告。ClickHouse的全称是Click Stream,Data WareHouse。
clickhouse可以做用户行为分析,流批一体
线性扩展和可靠性保障能够原生支持 shard + replication
clickhouse没有走hadoop生态,采用 Local attached storage 作为存储
1、列式存储:
行式存储的好处:
想查找某个人所有的属性时,可以通过一次磁盘查找加顺序读取就可以;但是当想查所有人的年龄时,需要不停的查找,或者全表扫描才行,遍历的很多数据都是不需要的。
列式存储的好处
2、DBMS功能:几乎覆盖了标准 SQL 的大部分语法,包括 DDL 和 DML、,以及配套的各种函数;用户管理及权限管理、数据的备份与恢复
3、多样化引擎:目前包括合并树、日志、接口和其他四大类20多种引擎。
4、高吞吐写入能力:
ClickHouse采用类LSM Tree的结构,数据写入后定期在后台Compaction。通过类 LSM tree的结构, ClickHouse在数据导入时全部是顺序append写,写入后数据段不可更改,在后台compaction时也是多个段merge sort后顺序写回磁盘。顺序写的特性,充分利用了磁盘的吞吐能力。
5、数据分区与线程及并行:
ClickHouse将数据划分为多个partition,每个partition再进一步划分为多个index granularity(索引粒度),然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理。在这种设计下, 单条 Query 就能利用整机所有 CPU。 极致的并行处理能力,极大的降低了查询延时。
所以, ClickHouse 即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端就是对于单条查询使用多cpu,就不利于同时并发多条查询。所以对于高 qps 的查询业务并不是强项。
6、ClickHouse 像很多 OLAP 数据库一样,单表查询速度优于关联查询,而且 ClickHouse的两者差距更为明显。
关联查询:clickhouse会将右表加载到内存。
C++可以利用硬件优势
摒弃了hadoop生态
数据底层以列式存储
利用单节点的多核并行处理
为数据建立索引一级、二级、稀疏索引
使用大量的算法处理数据
支持向量化处理
预先设计运算模型-预先计算
分布式处理数据
在线安装可以参考: clickhouse( 21.7.4)卸载及安装教程
# 1、linux关闭防火墙
# 2、CentOS 取消打开文件数限制
# (1)在 centos04 的 /etc/security/limits.conf 文件的末尾加入以下内容
[root@centos04 apps]# vim /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
* soft nproc 131072
* hard nproc 131072
# (2)/etc/security/limits.d/20-nproc.conf 文件的末尾加入以下内容
[root@centos04 apps]# vim /etc/security/limits.d/20-nproc.conf
* soft nofile 65536
* hard nofile 65536
* soft nproc 131072
* hard nproc 131072
# 3、安装依赖
[root@centos04 apps]# yum install -y libtool
[root@centos04 apps]# yum install -y *unixODBC*
# 4、取消 SELINUX
# 修改/etc/selinux/config 中的 SELINUX=disabled
[root@centos04 apps]# vim /etc/selinux/config
SELINUX=disabled
rpm包的下载地址: https://packages.clickhouse.com/rpm/stable/
# 1、下载ck相应版本的rpm安装包,上传到服务器
[root@centos04 clickhouse]# ll
total 938164
-rw-r--r--. 1 root root 78074 Jul 27 2021 clickhouse-client-21.7.3.14-2.noarch.rpm
-rw-r--r--. 1 root root 174283244 Jul 27 2021 clickhouse-common-static-21.7.3.14-2.x86_64.rpm
-rw-r--r--. 1 root root 786208040 Jul 27 2021 clickhouse-common-static-dbg-21.7.3.14-2.x86_64.rpm
-rw-r--r--. 1 root root 101969 Jul 27 2021 clickhouse-server-21.7.3.14-2.noarch.rpm
# 2、安装
[root@centos04 clickhouse]# rpm -ivh *.rpm
# 安装完毕后
# ck的bin命令在/usr/bin目录下
# ck的日志在/var/log/clickhouse-server目录下
# ck的数据和元数据在/var/lib/clickhouse目录下
# ck的配置文件在/etc/clickhouse-server目录下
# 3、把 :: 的注释打开, 这样的话才能让 ClickHouse 被除本机以外的服务器访问
[root@centos04 clickhouse]# vim /etc/clickhouse-server/config.xml
# 在这个文件中,有 ClickHouse 的一些默认路径配置,比较重要的
# 数据文件路径: /var/lib/clickhouse/
# 日志文件路径: /var/log/clickhouse-server/clickhouse-server.log
# 4、启动
[root@centos04 clickhouse-server]# clickhouse start
# 使用客户端进行连接
[root@centos04 clickhouse-server]# clickhouse-client -m
ClickHouse client version 21.7.3.14 (official build).
Connecting to localhost:9000 as user default.
Connected to ClickHouse server version 21.7.3 revision 54449.
centos04 :)
固定长度的整型,包括有符号整型或无符号整型。
整型范围:
Int8 - [-128 : 127]
Int16 - [-32768 : 32767]
Int32 - [-2147483648 : 2147483647]
Int64 - [-9223372036854775808 : 9223372036854775807]
无符号整型范围:
UInt8 - [0 : 255]
UInt16 - [0 : 65535]
UInt32 - [0 : 4294967295]
UInt64 - [0 : 18446744073709551615]
Float32 - float
Float64 – double
# 建议尽可能以整数形式存储数据。例如,将固定精度的数字转换为整数值,如时间用毫秒为单位表示,因为浮点型进行计算时可能引起四舍五入的误差。
centos04 :) select 1.0 - 0.9;
SELECT 1. - 0.9
Query id: 3cc23880-01f6-4fb8-b254-59b0bfbc85fd
┌──────minus(1., 0.9)─┐
│ 0.09999999999999998 │
└─────────────────────┘
1 rows in set. Elapsed: 0.009 sec.
# 使用场景:一般数据值比较小,不涉及大量的统计计算,精度要求不高的时候。
没有单独的类型来存储布尔值。可以使用 UInt8 类型,取值限制为 0 或 1。
有符号的浮点数,可在加、减和乘法运算过程中保持精度。对于除法,最低有效数字会被丢弃(不舍入)。
有三种声明:
➢ 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进行存储。
1) String
字符串可以任意长度的。它可以包含任意的字节集,包含空字节。
2) FixedString(N)
固定长度 N 的字符串, N 必须是严格的正自然数。当服务端读取长度小于 N 的字符串时候,通过在字符串末尾添加空字节来达到 N 字节长度。 当服务端读取长度大于 N 的字符串时候,将返回错误消息。
# 与 String 相比,极少会使用 FixedString,因为使用起来不是很方便。
包括 Enum8 和 Enum16 类型。 Enum 保存 'string'= integer 的对应关系。
# Enum8 用 'String'= Int8 对描述。
# Enum16 用 'String'= Int16 对描述。
# 示例如下:
# 1、创建一个带有一个枚举 Enum8('male' = 1, 'female' = 2) 类型的列
CREATE TABLE t_enum(
x Enum8('male' = 1, 'female' = 2)
)
ENGINE = TinyLog;
# 2、这个 x 列只能存储类型定义中列出的值: 'male'或'female'
centos04 :) INSERT INTO t_enum VALUES ('female'), ('male'), ('female');
INSERT INTO t_enum VALUES
Query id: 682f4ae3-39be-4f58-88cd-fe360a4a50c2
Ok.
3 rows in set. Elapsed: 0.007 sec.
centos04 :) select * from t_enum;
SELECT *
FROM t_enum
Query id: d52cb5bc-e0c6-4edd-8671-025df6d3ddad
┌─x──────┐
│ female │
│ male │
│ female │
└────────┘
3 rows in set. Elapsed: 0.010 sec.
# 3、如果尝试保存任何其他值, ClickHouse 抛出异常
centos04 :) insert into t_enum values('a');
INSERT INTO t_enum VALUES
Query id: ef441c8d-58d7-4fe5-bf16-ad2f0e4b0c1e
Exception on client:
Code: 36. DB::Exception: Unknown element 'a' for enum: data for INSERT was parsed from query
# 4、如果需要看到对应行的数值,则必须将 Enum 值转换为整数类型
centos04 :) SELECT CAST(x, 'Int8') FROM t_enum;
SELECT CAST(x, 'Int8')
FROM t_enum
Query id: c49b62fa-d809-4a71-90f5-8d844b24d272
┌─CAST(x, 'Int8')─┐
│ 2 │
│ 1 │
│ 2 │
└─────────────────┘
目前 ClickHouse 有三种时间类型
➢ Date 接受年-月-日的字符串比如 ‘2022-12-16’
➢ Datetime 接受年-月-日 时:分:秒的字符串比如 ‘2022-12-16 20:50:10’
➢ Datetime64 接受年-月-日 时:分:秒.亚秒的字符串比如‘2022-12-16 20:50:10.66’
日期类型,用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值。
Array(T): 由 T 类型元素组成的数组。
T 可以是任意类型,包含数组类型。 但不推荐使用多维数组, ClickHouse 对多维数组的支持有限。例如,不能在 MergeTree 表中存储多维数组。
# 创建数组
centos04 :) SELECT array(1, 2) AS x, toTypeName(x) ;
SELECT
[1, 2] AS x,
toTypeName(x)
Query id: cdc094b8-e6a2-430b-862f-282ddb7e0204
┌─x─────┬─toTypeName(array(1, 2))─┐
│ [1,2] │ Array(UInt8) │
└───────┴─────────────────────────┘
1 rows in set. Elapsed: 0.011 sec.
centos04 :) SELECT [1, 2] AS x, toTypeName(x);
SELECT
[1, 2] AS x,
toTypeName(x)
Query id: 9de1b20e-f042-4def-91ce-ed14711a0c10
┌─x─────┬─toTypeName([1, 2])─┐
│ [1,2] │ Array(UInt8) │
└───────┴────────────────────┘
1 rows in set. Elapsed: 0.009 sec.
还有很多数据类型,可以参考官方文档:https://clickhouse.com/docs/en/sql-reference
表引擎是 ClickHouse 的一大特色。可以说, 表引擎决定了如何存储表的数据。包括:
➢ 数据的存储方式和位置,写到哪里以及从哪里读取数据。
➢ 支持哪些查询以及如何支持。
➢ 并发数据访问。
➢ 索引的使用(如果存在)。
➢ 是否可以执行多线程请求。
➢ 数据复制参数。
表引擎的使用方式就是必须显式在创建表时定义该表使用的引擎,以及引擎使用的相关参数。
-- 以列文件的形式保存在磁盘上,不支持索引,没有并发控制。一般保存少量数据的小表,可以用于平时练习测试用。
create table t_tinylog (
id String,
name String
)
engine=TinyLog;
内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的性能表现(超过 10G/s)。
一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大(上限大概 1 亿行)的场景。
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);
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');
centos04 :) select * from t_order_mt;
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2020-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 1000.00 │ 2020-06-01 12:00:00 │
│ 102 │ sku_002 │ 2000.00 │ 2020-06-01 11: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_004 │ 2500.00 │ 2020-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
分区的目的主要是降低扫描的范围,优化查询速度 。如果不填,只会使用一个分区
。MergeTree 是以列文件+索引文件+表定义文件
组成的,但是如果设定了分区,那么这些文件就会保存到不同的分区目录中。
分区后,面对涉及跨分区的查询统计, ClickHouse 会以分区为单位并行处理
。
[root@centos04 t_order_mt]# pwd
/var/lib/clickhouse/data/default/t_order_mt
[root@centos04 t_order_mt]# ll
total 4
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:45 20200601_1_1_0
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:45 20200602_2_2_0
drwxr-x---. 2 clickhouse clickhouse 6 Mar 31 20:44 detached
-rw-r-----. 1 clickhouse clickhouse 1 Mar 31 20:44 format_version.txt
任何一个批次的数据写入都会产生一个临时分区,不会纳入任何一个已有的分区。写入后的某个时刻(大概 10-15 分钟后), ClickHouse 会自动执行合并操作
(也可以手动通过 optimize 执行),把临时分区的数据,合并到已有分区中。-- 再次执行上面的插入操作
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');
-- 可以看出,并没有立即进行合并
centos04 :) select * from t_order_mt;
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2020-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 1000.00 │ 2020-06-01 12:00:00 │
│ 102 │ sku_002 │ 2000.00 │ 2020-06-01 11: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_004 │ 2500.00 │ 2020-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2020-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 1000.00 │ 2020-06-01 12:00:00 │
│ 102 │ sku_002 │ 2000.00 │ 2020-06-01 11: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_004 │ 2500.00 │ 2020-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
# 此时,会把数据写入一个临时分区20200601_3_3_0和20200602_4_4_0
[root@centos04 t_order_mt]# ll
total 4
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:45 20200601_1_1_0
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:54 20200601_3_3_0
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:45 20200602_2_2_0
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:54 20200602_4_4_0
drwxr-x---. 2 clickhouse clickhouse 6 Mar 31 20:44 detached
-rw-r-----. 1 clickhouse clickhouse 1 Mar 31 20:44 format_version.txt
-- 手动执行合并
centos04 :) optimize table t_order_mt final;
-- 再次查询
centos04 :) select * from t_order_mt;
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2020-06-02 12:00:00 │
│ 102 │ sku_002 │ 600.00 │ 2020-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 1000.00 │ 2020-06-01 12:00:00 │
│ 101 │ sku_001 │ 1000.00 │ 2020-06-01 12:00:00 │
│ 102 │ sku_002 │ 2000.00 │ 2020-06-01 11: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 │ 2000.00 │ 2020-06-01 11: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_004 │ 2500.00 │ 2020-06-01 12:00:00 │
│ 102 │ sku_004 │ 2500.00 │ 2020-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
-- 此时生成了20200601_1_3_1和20200602_2_4_1两个目录,即为合并后的数据
[root@centos04 t_order_mt]# ll
total 4
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:45 20200601_1_1_0
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:57 20200601_1_3_1
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:54 20200601_3_3_0
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:45 20200602_2_2_0
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:57 20200602_2_4_1
drwxr-x---. 2 clickhouse clickhouse 203 Mar 31 20:54 20200602_4_4_0
drwxr-x---. 2 clickhouse clickhouse 6 Mar 31 20:44 detached
-rw-r-----. 1 clickhouse clickhouse 1 Mar 31 20:44 format_version.txt
ClickHouse 中的主键, 它只提供了数据的一级索引,但是却不是唯一约束
。 这就意味着是可以存在相同 primary key 的数据的。
主键的设定主要依据是查询语句中的 where 条件。根据条件通过对主键进行某种形式的二分查找,能够定位到对应的 index granularity,避免了全表扫描。
index granularity: 直接翻译的话就是索引粒度,指在稀疏索引中两个相邻索引对应数据的间隔。 ClickHouse 中的 MergeTree 默认是 8192
。官方不建议修改这个值,除非该列存在大量重复值,比如在一个分区中几万行才有一个不同数据。
稀疏索引的好处就是可以用很少的索引数据,定位更多的数据,代价就是只能定位到索引粒度的第一行,然后再进行进行一点扫描。
order by 设定了分区内的数据按照哪些字段顺序进行有序保存。
order by 是 MergeTree 中唯一一个必填项
,当用户不设置主键的情况,很多处理会依照 order by 的字段进行处理(比如去重和汇总)。
要求:主键必须是 order by 字段的前缀字段。
比如 order by 字段是 (id,sku_id) 那么主键必须是 id 或者(id,sku_id)
二级索引能够为非主键字段的查询发挥作用 。
-- 其中 GRANULARITY N 是设定二级索引对于一级索引粒度的粒度。
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 -- 为total_amount创建二级索引
) engine = MergeTree
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id, sku_id);
MergeTree 提供了可以管理数据表或者列的生命周期
的功能。
TTL即Time To Live ,表示数据存活的时间。在MergeTree中,可以为某个列字段或整张表设置TTL。当时间到达时,如果是列字段级别的TTL,则会删除这列的数据;如果是表级别的TTL,则会删除整张表的数据;如果同时设置了列级别和表级别的TTL,则会以先到期的那个为主。
无论是列级别还是表级别的TTL,都需要依托某个DataTime或Date类型
的字段,通过对这个时间字段的INTERVAL操作,来描述TTL的过期时间。
TTL time_col + INTERVAL 3 DAY
-- 上述语句表示数据的存活时间是time_col时间的3天之后
(1)列级别的TTL
如果想要设置列级别的TTL,在声明表字段的时候,为他们声明TTL表达式,主键字段不能被声明TTL,举例如下:
create table t_order_mt3(
id UInt32,
sku_id String,
total_amount Decimal(16,2) TTL create_time + interval 10 SECOND, -- 设置total_amount列生命周期为10秒
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('Asia/Shanghai')),
(107,'sku_002',2000.00,now('Asia/Shanghai')),
(110,'sku_003',600.00,now('Asia/Shanghai'));
-- 注意:当经过10s后,正常我们可以执行optimize table t_order_mt3 final语句直接查看表中结果,发现列total_amount下的数据变成0.0。
-- 但是此ClickHouse版本有bug,需要重启ClickHouse后,再执行optimize table t_order_mt3 final语句,才能看到效果。
-- 重启ClickHouse后,执行如下语句查看对应表中的数据
optimize table t_order_mt3 final;
select * from t_order_mt3 ;
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 106 │ sku_001 │ 0.00 │ 2023-04-03 20:35:40 │
│ 107 │ sku_002 │ 0.00 │ 2023-04-03 20:35:40 │
│ 110 │ sku_003 │ 0.00 │ 2023-04-03 20:35:40 │
└─────┴─────────┴──────────────┴─────────────────────┘
-- 如果想要修改列字段对的TTL,或是为已有字段添加TTL,则可以使用ALTER语句,如下:
ALTER TABLE tbl MODIFY COLUMN col type TTL col_time + INTERVAL 1 DAY
-- 例如:修改表 t_order_mt3 中的total_amount列,指定ttl过期时间为 60s
alter table t_order_mt3 modify column total_amount Decimal(16,2) ttl create_time + interval 60 second;
-- 查看t_order_mt3建表语句,total_amount列ttl 生效
centos04 :) show create table t_order_mt3;
CREATE TABLE default.t_order_mt3
(
`id` UInt32,
`sku_id` String,
`total_amount` Decimal(16, 2) TTL create_time + toIntervalSecond(60), -- 发现已经生效
`create_time` DateTime
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(create_time)
PRIMARY KEY id
ORDER BY (id, sku_id)
SETTINGS index_granularity = 8192
(2)表级别
在ClickHouse中我们还可以对整张表设置TTL,需要在建表时在表参数中指定TTL表达式,当TTL触发时,满足过期时间的数据行将被整行删除。
-- 数据会在 create_time 之后 10 秒丢失 。
alter table t_order_mt3 MODIFY TTL create_time + INTERVAL 10 SECOND;
-- 或者在建表的时候直接指定表级别的TTL
-- 创建表t_mt3,表级别指定TTL ,数据10s过期
CREATE TABLE t_mt3(
id UInt8,
name String,
age UInt8 ,
gender String,
create_time DateTime
) engine=MergeTree
order by id
TTL create_time + INTERVAL 10 SECOND;
-- 涉及判断的字段必须是 Date 或者 Datetime 类型,推荐使用分区的日期字段。
-- 能够使用的时间周期:
- SECOND
- MINUTE
- HOUR
- DAY
- WEEK
- MONTH
- QUARTER
- YEAR
ReplacingMergeTree 是 MergeTree 的一个变种,它存储特性完全继承 MergeTree,只是多了一个去重的功能。 尽管 MergeTree 可以设置主键,但是 primary key 其实没有唯一约束的功能。如果想处理掉重复的数据,可以借助这个 ReplacingMergeTree
。
数据的去重只会在合并的过程中出现。 合并会在未知的时间在后台进行,所以你无法预先作出计划。有一些数据可能仍未被处理。
如果表经过了分区,去重只会在分区内部进行去重,不能执行跨分区的去重。
所以 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() 填入的参数为版本字段,重复数据保留版本字段值最大的。如果不填版本字段,默认按照插入顺序保留最后一条。
insert into t_order_rmt values
(101,'sku_001',1000.00,'2022-06-01 12:00:00') ,
(102,'sku_002',2000.00,'2022-06-01 11:00:00'),
(102,'sku_004',2500.00,'2022-06-01 12:00:00'),
(102,'sku_002',2000.00,'2022-06-01 13:00:00'),
(102,'sku_002',12000.00,'2022-06-01 13:00:00'),
(102,'sku_002',600.00,'2022-06-02 12:00:00');
-- 可以发现在插入数据时候,在分区内进行去重
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2022-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 1000.00 │ 2022-06-01 12:00:00 │
│ 102 │ sku_002 │ 12000.00 │ 2022-06-01 13:00:00 │
│ 102 │ sku_004 │ 2500.00 │ 2022-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
-- 再次插入
insert into t_order_rmt values
(102,'sku_002',2000.00,'2022-06-01 13:00:00'),
(102,'sku_002',12000.00,'2022-06-01 13:00:00'),
(102,'sku_002',600.00,'2022-06-02 12:00:00');
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 1000.00 │ 2022-06-01 12:00:00 │
│ 102 │ sku_002 │ 12000.00 │ 2022-06-01 13:00:00 │
│ 102 │ sku_004 │ 2500.00 │ 2022-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2022-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 12000.00 │ 2022-06-01 13:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2022-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
-- 手动合并后再次查询,发现按照id, sku_id进行了去重
OPTIMIZE TABLE t_order_rmt FINAL;
centos04 :) select * from t_order_rmt;
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2022-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 1000.00 │ 2022-06-01 12:00:00 │
│ 102 │ sku_002 │ 12000.00 │ 2022-06-01 13:00:00 │
│ 102 │ sku_004 │ 2500.00 │ 2022-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
➢ 实际上是使用 order by 字段作为唯一键
➢ 去重不能跨分区
➢ 只有同一批插入或合并分区时才会进行去重
➢ 认定重复的数据保留,版本字段值最大的
➢ 如果版本字段相同则按插入顺序保留最后一笔
对于不查询明细,只关心以维度进行汇总聚合结果的场景。如果只使用普通的 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,'2022-06-01 12:00:00'),
(102,'sku_004',2500.00,'2022-06-01 12:00:00'),
(102,'sku_002',2000.00,'2022-06-01 11:00:00'),
(102,'sku_002',2000.00,'2022-06-01 13:00:00'),
(102,'sku_002',12000.00,'2022-06-01 13:00:00'),
(102,'sku_002',600.00,'2020-06-02 12:00:00');
centos04 :) select * from t_order_smt;
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2020-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 1000.00 │ 2022-06-01 12:00:00 │
│ 102 │ sku_002 │ 16000.00 │ 2022-06-01 11:00:00 │
│ 102 │ sku_004 │ 2500.00 │ 2022-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
-- 再次
insert into t_order_smt values
(101,'sku_001',1000.00,'2022-06-01 12:00:00'),
(102,'sku_004',2500.00,'2022-06-01 12:00:00'),
(102,'sku_002',2000.00,'2022-06-01 11:00:00'),
(102,'sku_002',2000.00,'2022-06-01 13:00:00');
-- 手动合并后再次查询
OPTIMIZE TABLE t_order_smt FINAL;
centos04 :) select * from t_order_smt;
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 101 │ sku_001 │ 2000.00 │ 2022-06-01 12:00:00 │
│ 102 │ sku_002 │ 20000.00 │ 2022-06-01 11:00:00 │
│ 102 │ sku_004 │ 5000.00 │ 2022-06-01 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 102 │ sku_002 │ 600.00 │ 2020-06-02 12:00:00 │
└─────┴─────────┴──────────────┴─────────────────────┘
可以得到以下结论
➢ 以 SummingMergeTree()中指定的列作为汇总数据列
➢ 可以填写多列必须数字列,如果不填,以所有非维度列且为数字列的字段为汇总数据列
➢ 以 order by 的列为准,作为维度列
➢ 其他的列按插入顺序保留第一行
➢ 不在一个分区的数据不会被聚合
➢ 只有在同一批次插入或分片合并时才会进行聚合
设计聚合表的话,唯一键值、流水号可以去掉,所有字段全部是维度、度量或者时间戳。
如果要是获取汇总值,还是需要使用 sum 进行聚合 ,可能会包含一些还没来得及聚合的临时明细。
ClickHouse 提供了 Delete 和 Update 的能力,这类操作被称为 Mutation
查询,它可以看做 Alter 的一种。
虽然可以实现修改和删除,但是和一般的 OLTP 数据库不一样, Mutation 语句是一种很“重”的操作,而且不支持事务
。“重”的原因主要是每次修改或者删除都会导致放弃目标数据的原有分区, 重建新分区
。所以尽量做批量的变更,不要进行频繁小数据的操作。
由于操作比较“重”,所以 Mutation 语句分两步执行,同步执行的部分其实只是进行新增数据新增分区和并把旧分区打上逻辑上的失效标记。直到触发分区合并的时候,才会删除旧数据释放磁盘空间
,一般不会开放这样的功能给用户,由管理员完成。
-- (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;
-- ClickHouse 基本上与标准 SQL 差别不大
➢ 支持子查询
➢ 支持 CTE(Common Table Expression 公用表表达式 with 子句)
➢ 支持各种 JOIN, 但是 JOIN 操作无法使用缓存,所以即使是两次相同的 JOIN 语句,ClickHouse 也会视为两条新 SQL
➢ 窗口函数
➢ 不支持自定义函数
➢ GROUP BY 操作增加了 with rollup\with cube\with total 用来计算小计和总计。
(1) 启动 zookeeper 集群
(2) 在 centos01的/etc/clickhouse-server/config.d 目录下创建一个名为 metrika.xml的配置文件,内容如下:
<yandex>
<zookeeper-servers>
<node index="1">
<host>centos01host>
<port>2181port>
node>
<node index="2">
<host>centos02host>
<port>2181port>
node>
<node index="3">
<host>centos03host>
<port>2181port>
node>
zookeeper-servers>
yandex>
注:也可以不创建外部文件,直接在 config.xml 中指定
(3) 将 metrika.xml同步到 centos02 上
(4) 在 centos01的/etc/clickhouse-server/config.xml 中增加
<zookeeper incl="zookeeper-servers" optional="true" />
<include_from>/etc/clickhouse-server/config.d/metrika.xmlinclude_from>
(5) /etc/clickhouse-server/config.xml同步到 centos02 上
分别在 centos01 和 centos02上启动 ClickHouse 服务
注意:因为修改了配置文件,如果以前启动了服务需要重启clickhouse restart
( 6) 在 centos01 和 centos02 上分别建表
副本只能同步数据,不能同步表结构,所以我们需要在每台机器上自己手动建表
。
-- centos01上创建表
create table t_order_rep (
id UInt32,
sku_id String,
total_amount Decimal(16,2),
create_time Datetime
) engine =ReplicatedMergeTree('/clickhouse/table/01/t_order_rep','rep_01')
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id,sku_id);
-- centos02上创建表
create table t_order_rep (
id UInt32,
sku_id String,
total_amount Decimal(16,2),
create_time Datetime
) engine =ReplicatedMergeTree('/clickhouse/table/01/t_order_rep','rep_02')
partition by toYYYYMMDD(create_time)
primary key (id)
order by (id,sku_id);
-- 参数解释
ReplicatedMergeTree 中,
-- 第一个参数是分片的 zk_path 一般按照: /clickhouse/table/{shard}/{table_name} 的格式写,如果只有一个分片就写 01 即可。
-- 第二个参数是副本名称, 相同的分片副本名称不能相同。
--- 在centos01上插入数据
insert into t_order_rep2 values
(101,'sku_001',1000.00,'2022-06-01 12:00:00'),
(102,'sku_002',2000.00,'2022-06-01 12:00:00'),
(103,'sku_004',2500.00,'2022-06-01 12:00:00'),
(104,'sku_002',2000.00,'2022-06-01 12:00:00'),
(105,'sku_003',600.00,'2022-06-02 12:00:00');
-- 在 centos02 上执行 select,可以查询出结果,说明副本配置正确
副本机制虽然能够提高数据的可用性,降低丢失风险,但是每台服务器实际上必须容纳全量数据, 对数据的横向扩容
没有解决。
要解决数据水平切分的问题,需要引入分片的概念。 通过分片把一份完整的数据进行切分,不同的分片分布到不同的节点上,再通过 Distributed 表引擎
把数据拼接起来一同使用。
Distributed 表引擎本身不存储数据
, 有点类似于 MyCat 之于 MySql,成为一种中间件,通过分布式逻辑表来写入、分发、路由来操作多台节点不同分片的分布式数据。
mycat入门可以参考: MySQL分库分表概念及实战、读取分离详解
注意: ClickHouse 的集群是表级别的,实际生产中, 一些企业仅做了高可用, 没有用分片,避免降低查询性能以及操作集群的复杂性。
可以参考:
ClickHouse部署3分片2副本集群 - 简书 (jianshu.com)