06 - Clickhouse的表引擎

目录

1、表引擎的使用

2、TinyLog

3、Memory

4、MergeTree

4.1、建表语句

4.2、插入数据

[4.3、partition by分区(可选)](#4.3、partition by分区(可选))

4.4、PartitionId

4.5、数据写入与分区合并

[4.6、primary key主键(可选)](#4.6、primary key主键(可选))

[4.7、order by(必须)](#4.7、order by(必须))

4.8、二级索引

4.9、数据TTL


1、表引擎的使用

表引擎决定了如何存储表的数据。包括:

  • 数据的存储方式和位置,写到哪里以及从哪里读取数据
  • 支持哪些查询以及如何支持
  • 并发数据访问
  • 索引的使用(如果存在)
  • 是否可以执行多线程请求
  • 数据复制参数

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

引擎的名称大小写敏感

**2、**TinyLog

以列文件的形式保存在磁盘上,不支持索引,没有并发控制。一般保存少量数据的小表,生产环境上作用有限。可以用于平时练习测试用。如下:

sql 复制代码
create table t_tinylog ( id String, name String) engine=TinyLog; 

3、Memory

内存引擎:数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的性能表现(超过10G/s)。一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大(上限大概1亿行)的场景。

4、MergeTree

ClickHouse中最强大的表引擎当属MergeTree(合并树)引擎 及该系列(*MergeTree)中的其他引擎,支持索引和分区,地位可以相当于innodb之于Mysql。

4.1、建表语句

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);

4.2、插入数据

sql 复制代码
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'); 
sql 复制代码
hallo102 :) select * from t_order_mt;

SELECT *
FROM t_order_mt

Query id: 3cf546e5-740d-4a8a-8169-de7fc6b4e056

┌──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 │
└─────┴─────────┴──────────────┴─────────────────────┘

6 rows in set. Elapsed: 0.005 sec.

hallo102 :)
  • 主键可重复
  • 根据日期分区,2020-06-01、2020-06-02共两个分区
  • 分区内根据id和sku_id排序

4.3、partition by分区(可选)

分区的目的主要是降低扫描的范围,优化查询速度

如果不填,只会使用一个分区

分区后,面对涉及跨分区的查询统计,ClickHouse会以分区为单位并行处理

bash 复制代码
[root@aliyun ~]# cd /var/lib/clickhouse/
[root@aliyun clickhouse]# ls
access  data  dictionaries_lib  flags  format_schemas  metadata  metadata_dropped  preprocessed_configs  status  store  tmp  user_files
  • data:数据存储的路径
  • metadata:表结构信息
bash 复制代码
[root@aliyun clickhouse]# cd metadata
[root@aliyun metadata]# ls
default  default.sql  system  system.sql
[root@aliyun metadata]# cd default
[root@aliyun default]# ls
t_enum.sql  t_order_mt.sql
bash 复制代码
[root@aliyun clickhouse]# cd data/
[root@aliyun data]# ls
default  system
[root@aliyun data]# cd default/
[root@aliyun default]# ls
t_enum  t_order_mt
[root@aliyun default]# cd t_order_mt/
[root@aliyun t_order_mt]# ls
20200601_1_1_0  20200602_2_2_0  detached  format_version.txt

20200601_1_1_0、20200602_2_2_0共两个分区目录

分区目录命名格式:PartitionId_MinBlockNum_MaxBlockNum_Level,分表代表分区值、最小分区块编号、最大分区块编号、合并层级

4.4、PartitionId

数据分区规则由分区ID决定,分区ID由partition by分区键决定。根据分区键字段类型,ID生成规则可分为:

  • **未定义分区键:**没有定义partition by,默认生成一个目录名为all的数据分区,所有数据均存放在all目录下
  • **整型分区键:**分区键为整型,直接用该整型值的字符串形式作为分区ID
  • **日期类分区键:**分区键为日期类型,或者可以转换为日期类型
  • **其他类型分区键:**String、Float类型等,通过128位的Hash算法娶妻Hash值作为分区ID

**MinBlockNum:**最小分区块编号,自增类型,从1开始向上递增。每产生一个新的目录分区就向上递增一个数字

**MaxBlockNum:**最大分区块编号,新创建的分区MinBlockNum等于MaxBlockNum的编号

**Level:**合并的层级,被合并的次数。合并次数越多,层级值越大

bash 复制代码
[root@aliyun t_order_mt]# cd 20200601_1_1_0
[root@aliyun 20200601_1_1_0]# ls
checksums.txt  columns.txt  count.txt  data.bin  data.mrk3  default_compression_codec.txt  minmax_create_time.idx  partition.dat  primary.idx
  • data.bin:数据文件
  • data.mrk3:标记文件,标记文件在idx索引文件和bin数据文件之间起到了桥梁作用
  • count.txt:有几条数据
  • default_compression_codec.txt:默认压缩格式
  • columns.txt:列的信息
  • primary.idx:主键索引文件
  • partition.dat与minmax_[Column].idx:如果使用了分区键,则会额外生成这2个文件,均使用二进制存储。partition.dat保存当前分区下分区表达式最终生成的值;minmax索引用于记录当前分区下分区字段对应原始数据的最小值和最大值。以t_order_mt的20200601分区为例,partition.dat中的值为20200601,minmax索引中保存的值为2020-06-01 12:00:002020-06-01 13:00:00

4.5、数据写入与分区合并

任何一个批次的数据写入都会产生一个临时分区,不会纳入任何一个已有的分区。写入后的某个时刻(大概10-15分钟后),ClickHouse会自动执行合并操作(等不及也可以手动通过optimize执行),把临时分区的数据,合并到已有分区中

optimize table xxxx final;

案例

执行插入操作

sql 复制代码
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');
sql 复制代码
hallo102 :) select * from t_order_mt;

SELECT *
FROM t_order_mt

Query id: 8afd18ae-7fd9-455e-9b33-0a3ba5e90a56

┌──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 │
└─────┴─────────┴──────────────┴─────────────────────┘

12 rows in set. Elapsed: 0.006 sec.

hallo102 :)

手动optimize之后

optimize table t_order_mt final;

sql 复制代码
hallo102 :) optimize table t_order_mt final;

OPTIMIZE TABLE t_order_mt FINAL

Query id: efe37202-45e6-4788-b270-fb7d1a67899c

Ok.

0 rows in set. Elapsed: 0.004 sec.

hallo102 :) select * from t_order_mt;

SELECT *
FROM t_order_mt

Query id: 2859dabe-ee02-486b-8cc4-3349c6b7a0a7

┌──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 │
└─────┴─────────┴──────────────┴─────────────────────┘

12 rows in set. Elapsed: 0.003 sec.

hallo102 :)

插入数据合并前:

root@aliyun t_order_mt\]# ls 20200601_1_1_0 20200601_3_3_0 20200602_2_2_0 20200602_4_4_0 detached format_version.txt

插入数据合并后:

root@aliyun t_order_mt\]# ls 20200601_1_1_0 20200601_1_3_1 20200601_3_3_0 20200602_2_2_0 20200602_2_4_1 20200602_4_4_0 detached format_version.txt

20200601_1_1_0和20200601_3_3_0分区合并为20200601_1_3_1,在真正合并的时候20200601_1_1_0和20200601_3_3_0分区会被清理

4.6、primary key主键(可选)

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

主键的设定主要依据是查询语句中的where条件

根据条件通过对主键进行某种形式的二分查找,能够定位到对应的index granularity,避免了全表扫描

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

稀疏索引

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

4.7、order by(必须)

order by设定了分区内的数据按照哪些字段顺序进行有序保存

order by是MergeTree中唯一一个必填项,甚至比primary key还重要,因为当用户不设置主键的情况,很多处理会依照order by的字段进行处理

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

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

4.8、二级索引

目前在ClickHouse的官网上二级索引的功能在v20.1.2.4之前是被标注为实验性的,在这个版本之后默认是开启的

1)老版本使用二级索引前需要增加设置

是否允许使用实验性的二级索引(v20.1.2.4开始,这个参数已被删除,默认开启)

set allow_experimental_data_skipping_indices=1;

2)创建测试表

sql 复制代码
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 N是设定二级索引对于一级索引粒度的粒度

minmax 索引的聚合信息是在一个index_granularity区间内数据的最小和最大值。以下图为例,假设index_granularity=8192且granularity=3,则数据会按照index_granularity划分为n等份,MergeTree从第0段分区开始,依次获取聚合信息。当获取到第3个分区时(granularity=3),则汇总并会生成第一行minmax索引(前3段minmax汇总后取值为[1, 9])

3)插入数据

sql 复制代码
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');

4)对比效果

root@aliyun \~\]# clickhouse-client --send_logs_level=trace \<\<\< 'select \* from t_order_mt2 where total_amount \> toDecimal32(900., 2)';

日志中可以看到二级索引能够为非主键字段的查询发挥作用

分区下文件skp_idx_a.idxskp_idx_a.mrk3为跳数索引文件:

bash 复制代码
[root@aliyun t_order_mt2]# ls
20200601_1_1_0  20200602_2_2_0  detached  format_version.txt
[root@aliyun t_order_mt2]# cd 20200601_1_1_0/
[root@aliyun 20200601_1_1_0]# ls
checksums.txt  count.txt  data.mrk3                      minmax_create_time.idx  primary.idx    skp_idx_a.mrk3
columns.txt    data.bin   default_compression_codec.txt  partition.dat           skp_idx_a.idx

4.9、数据TTL

MergeTree提供了可以管理数据 或者的生命周期的功能

1)列级TTL

sql 复制代码
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的列必须是日期类型且不能为主键

插入数据(请根据实际时间修改数据)

sql 复制代码
insert into t_order_mt3 values
(106,'sku_001',1000.00,'2024-11-17 19:46:00'),
(107,'sku_002',2000.00,'2024-11-17 19:46:00'),
(110,'sku_003',600.00,'2024-11-17 19:46:00');

手动合并,查看效果:到期后,指定的字段数据归0

sql 复制代码
hallo102 :) select * from t_order_mt3;

SELECT *
FROM t_order_mt3

Query id: ec75eabe-a151-41fd-b828-ea967bb830d9

┌──id─┬─sku_id──┬─total_amount─┬─────────create_time─┐
│ 106 │ sku_001 │         0.00 │ 2024-11-17 19:46:00 │
│ 107 │ sku_002 │         0.00 │ 2024-11-17 19:46:00 │
│ 110 │ sku_003 │         0.00 │ 2024-11-17 19:46:00 │
└─────┴─────────┴──────────────┴─────────────────────┘

3 rows in set. Elapsed: 0.009 sec.

hallo102 :)

2)表级TTL

下面的这条语句是数据会在create_time之后10秒丢失

sql 复制代码
alter table t_order_mt3 MODIFY TTL create_time + INTERVAL 10 SECOND;

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

能够使用的时间周期:

  • SECOND
  • MINUTE
  • HOUR
  • DAY
  • WEEK
  • MONTH
  • QUARTER
  • YEAR
相关推荐
编啊编程啊程23 分钟前
【029】智能停车计费系统
java·数据库·spring boot·spring·spring cloud·kafka
Leon-Ning Liu1 小时前
Oracle数据库常用视图:dba_datapump_jobs
数据库·oracle·dba
数据库生产实战1 小时前
Oracle 19C RAC下TRUNCATE TABLE的REUSE STORAGE选项作用和风险浅析!
数据库·oracle
小白银子2 小时前
零基础从头教学Linux(Day 60)
linux·数据库·mysql·oracle
瀚高PG实验室2 小时前
数据库安全配置指导
服务器·数据库·瀚高数据库
憋问我,我也不会2 小时前
MYSQL 命令
数据库·mysql
24K老游3 小时前
postgres15 flink cdc同步测试
数据库
无泡汽水4 小时前
MySQL入门练习50题
数据库·mysql
JIngJaneIL4 小时前
助农惠农服务平台|助农服务系统|基于SprinBoot+vue的助农服务系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·助农惠农服务平台
云外天ノ☼4 小时前
待办事项全栈实现:Vue3 + Node.js (Koa) + MySQL深度整合,构建生产级任务管理系统的技术实践
前端·数据库·vue.js·mysql·vue3·koa·jwt认证