![](https://img-blog.csdnimg.cn/img_convert/6b2ff3f18d46770ded39f6a6fcfe7db0.png)
表引擎是ClickHouse设计实现中的一大特色。
表引擎在 ClickHouse 中的作用十分关键,直接决定了数据如何存储和读取、是否支持并发读写、是否支持 index、支持的 query 种类、是否支持主备复制等。
1、表引擎概述
1.1 介绍
ClickHouse 提供了大约 28 种表引擎,各有各的用途,比如有 Log 系列用来做小表数据分析,MergeTree 系列用来做大数据量分析,而 Integration 系列则多用于外表数据集成。再考虑复制表Replicated 系列,分布式表 Distributed 等,纷繁复杂,新用户上手选择时常常感到迷惑。
ClickHouse表引擎一共分为四个系列,分别是Log、MergeTree、Integration、Special。其中包含了两种特殊的表引擎Replicated、Distributed,功能上与其他表引擎正交,根据场景组合使用。
最强大的表引擎当属 MergeTree (合并树)引擎及该系列(*MergeTree)中的其他引擎。对于大多数正式的任务,推荐使用MergeTree 族中的引擎。因为只有合并树系列的表引擎才支持主键索引、数据分区、数据副本和数据采样这些特性,同时也只有此系列的表引擎支持ALTER相关操作。
Log、Special、Integration 主要用于特殊用途,场景相对有限。MergeTree 系列才是官方主推的存储引擎,支持几乎所有 ClickHouse 核心功能。
1.2 表引擎概览
一共分为四个系列,分别是Log、MergeTree、Integration、Special。其中包含了两种特殊的表引擎Replicated、Distributed,功能上与其他表引擎正交。
![](https://img-blog.csdnimg.cn/img_convert/9896c86459fdf8a349fc5c68dab05538.jpeg)
表引擎(即表的类型)决定了:
(1)数据的存储方式和位置,写到哪里以及从哪里读取数据
(2)支持哪些查询以及如何支持。
(3)并发数据访问。
(4)索引的使用(如果存在)。
(5)是否可以执行多线程请求。
(6)数据复制参数。
ClickHouse 的表引擎有很多,下面介绍其中几种,对其他引擎有兴趣的可以去查阅官方文档:https://clickhouse.yandex/docs/zh/operations/table_engines/
2、Ordinary默认数据库引擎
Ordinary就是ClickHouse中默认引擎,如果不指定数据库引擎创建的就是Ordinary数据库引擎,在这种数据库下面可以使用任意表引擎。
3、Log系列表引擎
Log系列表引擎功能相对简单,主要用于快速写入小表(1百万行左右的表),然后全部读出的场景。当你需要快速写入许多小表(最多约100万行)并在以后整体读取它们时,该类型的引擎是最有效的。
几种Log表引擎的共性是:
1、数据被顺序append写到磁盘上;
2、不支持delete、update修改数据;
3、不支持index;
4、不支持原子性写;如果某些操作(异常的服务器关闭)中断了写操作,则可能会获得带有损坏数据的表。
5、insert会阻塞select操作。当向表中写入数据时,针对这张表的查询会被阻塞,直至写入动作结束。
该类型的引擎有:
1、TinyLog
2、StripeLog
3、Log
3.1 TinyLog
TinyLog是Log系列引擎中功能简单、性能较低的引擎。
它的存储结构由数据文件和元数据两部分组成。其中,数据文件是按列独立存储的,也就是说每一个列字段都对应一个文件。
由于TinyLog数据存储不分块,所以不支持并发数据读取,该引擎适合一次写入,多次读取的场景,对于处理小批量中间表的数据可以使用该引擎,这种引擎会有大量小文件,性能会低。
不支持索引
该引擎没有并发控制:
如果同时从表中读取和写入数据,则读取操作将抛出异常;
如果同时写入多个查询中的表,则数据将被破坏。
Log 引擎不支持 ALTER UPDATE 和 ALTER DELETE 操作。
示例:
-- 在ch中创建库 newdb,并使用
create database newdb;
use newdb;
-- 创建表t_tinylog 表,使用TinyLog引擎
create table t_tinylog(
id UInt8
,name String
,age UInt8
) engine=TinyLog;
-- 向表中插入数据
insert into t_tinylog values (1,'张三',18),(2,'李四',19),(3,'王五',20);
-- 查询表中的数据
select * from t_tinylog;
┌─id─┬─name─┬─age─┐
│ 1 │ 张三 │ 18 │
│ 2 │ 李四 │ 19 │
│ 3 │ 王五 │ 20 │
└────┴──────┴─────┘
不能删除数据
-- 在表中删除一条数据,这里是不支持delete。
delete from t_tinylog where id = 1;//语句不适合CH
alter table t_tinylog delete where id = 1;
:Exception: Mutations are not supported by storage TinyLog.
3.2 StripeLog
相比TinyLog而言,StripeLog数据存储会划分块,每次插入对应一个数据块,拥有更高的查询性能(拥有.mrk标记文件,支持并行查询)。
StripeLog 引擎将所有列存储在一个文件中,使用了更少的文件描述符。对每一次 Insert 请求,ClickHouse 将数据块追加在表文件的末尾,逐列写入。
StripeLog 引擎不支持 ALTER UPDATE 和 ALTER DELETE 操作。
-- 在库 newdb中创建表 t_stripelog,使用StripeLog引擎
create table t_stripelog(
id UInt8
,name String
,age UInt8
) engine = StripeLog;
#向表t_stripelog中插入数据,这里插入分多次插入,会将数据插入不同的数据块中
node1 :) insert into t_stripelog values (1,'张三',18);
node1 :) insert into t_stripelog values (2,'李四',19);
#查询表 t_stripelog数据
node1 :) select * from t_stripelog;
SELECT *
FROM t_stripelog
┌─id─┬─name─┬─age─┐
│ 1 │ 张三 │ 18 │
└────┴──────┴─────┘
┌─id─┬─name─┬─age─┐
│ 2 │ 李四 │ 19 │
└────┴──────┴─────┘
2 rows in set. Elapsed: 0.003 sec.
3.3 Log
Log引擎表适用于临时数据,一次性写入、测试场景。Log引擎结合了TinyLog表引擎和StripeLog表引擎的长处,是Log系列引擎中性能最高的表引擎。
Log表引擎会将每一列都存在一个文件中,对于每一次的INSERT操作,会生成数据块,经测试,数据块个数与当前节点的core数一致。
-- 在newdb中创建表t_log 使用Log表引擎
create table t_log(id UInt8 ,name String ,age UInt8 ) engine = Log;
-- 向表 t_log中插入数据,分多次插入,插入后数据存入数据块
insert into t_log values (1,'张三',18);
insert into t_log values (2,'李四',19);
insert into t_log values (3,'王五',20);
insert into t_log values (4,'马六',21);
insert into t_log values (5,'田七',22);
-- 查询表t_log中的数据
select * from t_log;
┌─id─┬─name─┬─age─┐
│ 1 │ 张三 │ 18 │
│ 2 │ 李四 │ 19 │
└────┴─────┴─────┘
┌─id─┬─name─┬─age─┐
│ 3 │ 王五 │ 20 │
│ 4 │ 马六 │ 21 │
│ 5 │ 田七 │ 22 │
└────┴─────┴─────┘
4、Special系列表引擎
4.1 Memory
Memory表引擎直接将数据保存在内存中,ClickHouse中的Memory表引擎具有以下特点:
Memory 引擎以未压缩的形式将数据存储在 RAM 中,数据完全以读取时获得的形式存储。
并发数据访问是同步的,锁范围小,读写操作不会相互阻塞。
不支持索引。
查询是并行化的,在简单查询上达到最大速率(超过10 GB /秒),在相对较少的行(最多约100,000,000)上有高性能的查询。
没有磁盘读取,不需要解压缩或反序列化数据,速度更快(在许多情况下,与 MergeTree 引擎的性能几乎一样高)。
重新启动服务器时,表存在,但是表中数据全部清空。
Memory引擎多用于测试。
示例:
重启clickhouse服务,Memory表存在,但数据丢失
-- 在 newdb中创建表 t_memory ,表引擎使用Memory
CREATE TABLE t_memory
(
`id` UInt8,
`name` String,
`age` UInt8
)
ENGINE = Memory
-- 向表 t_memory中插入数据
insert into t_memory values (1,'张三',18),(2,'李四',19),(3,'王五',20);
-- 查询表t_memory中的数据
select * from t_memory;
┌─id─┬─name─┬─age─┐
│ 1 │ 张三 │ 18 │
│ 2 │ 李四 │ 19 │
│ 3 │ 王五 │ 20 │
└────┴──────┴─────┘
-- 重启clickhouse 服务
service clickhouse-server restart
-- 进入 newdb 库,查看表 t_memory数据,数据为空。
select * from t_memory;
4.2 Merge
Merge 引擎 (不要跟 MergeTree 引擎混淆) 本身不存储数据,但可用于同时从任意多个其他的表中读取数据,这里需要多个表的结构相同,并且创建的Merge引擎表的结构也需要和这些表结构相同才能读取。
读是自动并行的,不支持写入。读取时,那些被真正读取到数据的表如果设置了索引,索引也会被使用。
示例:
merge表引擎根据正则查询表名,聚合相同表结构的数据
-- 1、创建3张表,表结构相同,并插入数据
-- 在newdb库中创建表m_t1,并插入数据
create table m_t1 (
id UInt8 ,name String,age UInt8
) engine = TinyLog;
insert into m_t1 values (1,'张三',18),(2,'李四',19)
-- 在newdb库中创建表m_t2,并插入数据
create table m_t2 (
id UInt8 ,name String,age UInt8
) engine = TinyLog;
insert into m_t2 values (3,'王五',20),(4,'马六',21)
-- 在newdb库中创建表m_t3,并插入数据
create table m_t3 (
id UInt8 ,name String,age UInt8
) engine = TinyLog;
insert into m_t3 values (5,'田七',22),(6,'赵八',23)
-- 2、在newdb库中创建表t_merge,使用Merge引擎,匹配m开头的表
create table t_merge (
id UInt8,name String,age UInt8
) engine = Merge(newdb,'^m');
-- 查询 t_merge表中的数据
select * from t_merge;
┌─id─┬─name──┬─age─┐
│ 1 │ 张三 │ 18 │
│ 2 │ 李四 │ 19 │
└────┴───────┴─────┘
┌─id─┬─name─┬─age─┐
│ 3 │ 王五 │ 20 │
│ 4 │ 马六 │ 21 │
└────┴──────┴─────┘
┌─id─┬─name─┬─age─┐
│ 5 │ 田七 │ 22 │
│ 6 │ 赵八 │ 23 │
└────┴──────┴─────┘
4.3 Distributed
Distributed是ClickHouse中分布式引擎,使用分布式引擎声明的表才可以在其他节点访问与操作。
Distributed引擎和Merge引擎类似,本身不存放数据,功能是在不同的server上把多张相同结构的物理表合并为一张逻辑表。
1> 语法
Distributed(cluster_name, database_name, table_name[, sharding_key])
对以上语法解释:
cluster_name:集群名称,与集群配置中的自定义名称相对应。配置在/etc/clickhouse-server/config.d/metrika.xml文件中,如下图:
![](https://img-blog.csdnimg.cn/img_convert/a1a557523235f1de0d75ceb39cda0c16.png)
database_name:数据库名称。
table_name:表名称。
sharding_key:可选的,用于分片的key值,在数据写入的过程中,分布式表会依据分片key的规则,将数据分布到各个节点的本地表。
注意:创建分布式表是读时检查的机制,也就是说对创建分布式表和本地表的顺序并没有强制要求。
2> 示例
-- 1、在node1、node2、node3节点上启动ClickHouse 服务
-- 2、使用默认的default库,在每个节点上创建表 test_table
node1 :) create table test_local (id UInt8,name String) engine= TinyLog
node2 :) create table test_local (id UInt8,name String) engine= TinyLog
node3 :) create table test_local (id UInt8,name String) engine= TinyLog
-- 3、在node1上创建分布式表 t_distributed,表引擎使用 Distributed 引擎
node1 :) create table t_distributed(id UInt8,name String) engine = Distributed(ckcluster,default,test_local,id);
-- 注意:以上分布式表 t_distributed 只存在与node1节点的clickhouse中。
-- 4、分别在node1、node2、node3节点上向表test_local中插入2条数据
node1 :) insert into test_local values (1,'张三'),(2,'李四');
node2 :) insert into test_local values (3,'王五'),(4,'马六');
node3 :) insert into test_local values (5,'田七'),(6,'赵八');
-- 5、查询分布式表 t_distributed 中的数据
node1 :) select * from t_distributed;
┌─id─┬─name──┐
│ 1 │ 张三 │
│ 2 │ 李四 │
└────┴───────┘
┌─id─┬─name─┐
│ 5 │ 田七 │
│ 6 │ 赵八 │
└────┴──────┘
┌─id─┬─name─┐
│ 3 │ 王五 │
│ 4 │ 马六 │
└────┴──────┘
-- 6、向分布式表 t_distributed 中插入一些数据,然后查询 node1、node2、node3节点上的test_local数据,发现数据已经分布式存储在不同节点上
node1 :) insert into t_distributed values (7,'zs'),(8,'ls'),(9,'ww'),(10,'ml'),(11,'tq'),(12,'zb');
-- node1查询本地表 test_local
node1 :) select * from test_local;
┌─id─┬─name─┐
│ 1 │ 张三 │
│ 2 │ 李四 │
│ 9 │ ww │
│ 12 │ zb │
└────┴──────┘