MySQL 的数据表有 InnoDB 和 MyISAM 存储引擎,不同的存储引擎提供不同的存储机
制、索引方式等功能,也可以称之为表类型。在 ClickHouse 中也有表引擎。
表引擎在 ClickHouse 中的作用十分关键,直接决定了数据如何存储和读取、是否支
持并发读写、是否支持 index 索引、支持的 query 种类、是否支持主备复制等。
ClickHouse 提供了大约 28 种表引擎,各有各的用途,比如有 Log 系列用来做小表
数据分析,MergeTree 系列用来做大数据量分析,而 Integration 系列则多用于外表数
据集成。再考虑复制表 Replicated 系列,分布式表 Distributed 等
ClickHouse 表引擎一共分为四个系列,分别是 Log 系列、MergeTree 系列、Integration 系列、Special 系列。其中包含了两种特殊的表引擎 Replicated、Distributed,功能上与其他表引擎正交,根据场景组合使用。
Log系列表引擎
Log 系列表引擎功能相对简单,主要用于快速写入小表(1 百万行左右的表),然后全
部读出的场景,即一次写入,多次查询。Log 系列表引擎包含:TinyLog、StripeLog、
Log 三种引擎。
几种 Log 表引擎的共性是:
数据被顺序 append 写到本地磁盘上。
不支持 delete、update 修改数据。
不支持 index(索引)。
不支持原子性写。如果某些操作(异常的服务器关闭)中断了写操作,则可能会获
得带有损坏数据的表。
insert 会阻塞 select 操作。当向表中写入数据时,针对这张表的查询会被阻塞,
直至写入动作结束。
它们彼此之间的区别是:
TinyLog:不支持并发读取数据文件,查询性能较差;格式简单,适合用来暂存
中间数据。
StripLog:支持并发读取数据文件,查询性能比 TinyLog 好;将所有列存储在
同一个大文件中,减少了文件个数。
Log:支持并发读取数据文件,查询性能比 TinyLog 好;每个列会单独存储在一
个独立文件中
TinyLog
TinyLog 是 Log 系列引擎中功能简单、性能较低的引擎。
它的存储结构由数据文件和元数据两部分组成。其中,数据文件是按列独立存储的,也
就是说每一个列字段都对应一个文件。 由于 TinyLog 数据存储不分块,所以不支持并发数据读取,该引擎适合一次写入,多
次读取的场景,对于处理小批量中间表的数据可以使用该引擎,这种引擎会有大量小文件,
性能会低
create table t_tinylog(id UInt8,name String,age UInt8) engine=TinyLog;
insert into t_tinylog values (1,'张三',18),(2,'李四',19),(3,' 王五',20);
#在表中删除一条数据,这里是不支持 delete。
alter table t_tinylog delete where id = 1;
当 在 newdb 库 中 创 建 表 t_tinylog 后 , 在 ClickHouse 保 存 数 据 的 目 录 /var/lib/clickhouse/data/newdb/下会多一个 t_tinylog 目录在向表 t_tinylog 中插入数据后,进入“
t_tinylog”目录,查看目录下的文件, 如下图所示:
表 t_tinylog 中的每个列都单独对应一个*.bin 文件,同时还有一 个 sizes.json 文件存储元数据,记录了每个 bin 文件中数据大小
StripeLog
StripeLog 数据存储会划分块,每次插入对应一个数据块,拥
有更高的查询性能(拥有.mrk 标记文件,支持并行查询)。StripeLog 引擎将所有列存
储在一个文件中,使用了更少的文件描述符。对每一次 Insert 请求,ClickHouse 将
数据块追加在表文件的末尾,逐列写入。StripeLog 引擎不支持 ALTER UPDATE 和
ALTER DELETE 操作。
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);
当 在 newdb 库 中创 建表 t_stripelog 后 ,在 ClickHouse 保 存数 据的 目录 /var/lib/clickhouse/data/newdb/下会多一个 t_stripelog 目录,如图所示:
data.bin:数据文件,所有列字段都写入 data.bin 文件中。
index.mrk:数据标记文件,保存了数据在 data.bin 文件中的位置信息,即每
个插入数据列的 offset 信息,利用数据标记能够使用多个线程,并行度取
data.bin 压缩数据,提升查询性能。
sizes.json:元数据文件,记录了 data.bin 和 index.mrk 大小信息。
Log
Log 引擎表适用于临时数据,一次性写入、测试场景。Log 引擎结合了 TinyLog 表引
擎和 StripeLog 表引擎的长处,是 Log 系列引擎中性能最高的表引擎。
Log 表引擎会将每一列都存在一个文件中,对于每一次的 INSERT 操作,会生成数据
块,经测试,数据块个数与当前节点的 core 数一致。
create table t_log(id UInt8 ,name String ,age UInt8 ) engine = Log;
#向表 t_log 中插入数据,分多次插入,插入后数据存入数据块
node1 :) insert into t_log values (1,'张三',18);
node1 :) insert into t_log values (2,'李四',19);
node1 :) insert into t_log values (3,'王五',20);
node1 :) insert into t_log values (4,'马六',21);
node1 :) insert into t_log values (5,'田七',22);
当 在 newdb 库 中 创 建 表 t_log 后 , 在 ClickHouse 保 存 数 据 的 目 录
/var/lib/clickhouse/data/newdb/下会多一个 t_log 目录,如图所示:
我们发现表 t_log 中的每个列都对应一个*.bin 文件。其他两个文件的解释如下:
__marks.mrk:数据标记,保存了每个列文件中的数据位置信息,利用数据标记
能够使用多个线程,并行度取 data.bin 压缩数据,提升查询性能。
sizes.json:记录了*.bin 和__mark.mrk 大小的信息。
Special系列引擎
Memory 表引擎直接将数据保存在内存中,ClickHouse 中的 Memory 表引擎具有以
下特点:
Memory 引擎以未压缩的形式将数据存储在 RAM 中,数据完全以读取时获得的形
式存储。
并发数据访问是同步的,锁范围小,读写操作不会相互阻塞。
不支持索引。
查询是并行化的,在简单查询上达到最大速率(超过 10 GB /秒),在相对较少
的行(最多约 100,000,000)上有高性能的查询。
没有磁盘读取,不需要解压缩或反序列化数据,速度更快(在许多情况下,与
MergeTree 引擎的性能几乎一样高)。
重新启动服务器时,表存在,但是表中数据全部清空。
Memory 引擎多用于测试。
create table t_memory(id UInt8 ,name String, age UInt8) engine = Memory;
注意:”Memory”表引擎写法固定,不能小写。同时创建好表 t_memory 后,在对应
的磁盘目录/var/lib/clickhouse/data/newdb 下没有“
t_memory”目录,基于内存
存储,当重启 ClickHouse 服务后,表 t_memory 存在,但是表中数据全部清空。
Merge
Merge 引擎 (不要跟 MergeTree 引擎混淆) 本身不存储数据,但可用于同时从任
意多个其他的表中读取数据,这里需要多个表的结构相同,并且创建的 Merge 引擎表的结
构也需要和这些表结构相同才能读取。 读是自动并行的,不支持写入。读取时,那些被真正读取到数据的表如果设置了索引, 索引也会被使用。
Merge 引擎的参数:一个数据库名和一个用于匹配表名的正则表达式:
Merge(数据库, 正则表达式)
例如:Merge(hits, '^WatchLog') 表示数据会从 hits 数据库中表名匹配正则
‘
^WatchLog’ 的表中读取。
注意:当选择需要读取的表时,会匹配正则表达式匹配上的表,如果当前 Merge 表的
名称也符合正则表达式匹配表名,这个 Merge 表本身会自动排除,以避免进入递归死循环,
当然也可以创建两个相互无限递归读取对方数据的 Merge 表,但这并没有什么意义。
例子:
create table m_t1 (id UInt8 ,name String,age UInt8) engine = TinyLog;
node1 :) insert into m_t1 values (1,'张三',18),(2,'李四',19)
#在 newdb 库中创建表 m_t2,并插入数据
node1 :) create table m_t2 (id UInt8 ,name String,age UInt8) engine = TinyLog;
node1 :) insert into m_t2 values (3,'王五',20),(4,'马六',21)
#在 newdb 库中创建表 m_t3,并插入数据
node1 :) create table m_t3 (id UInt8 ,name String,age UInt8) engine = TinyLog;
node1 :) insert into m_t3 values (5,'田七',22),(6,'赵八',23)
#在 newdb 库中创建表 t_merge,使用 Merge 引擎,匹配 m 开头的表
node1 :) create table t_merge (id UInt8,name String,age UInt8) engine = Merge(newdb,'^m');
#查询 t_merge 表中的数据
node1 :) select * from t_merge;
Distributed
Distributed 是 ClickHouse 中 分 布 式 引 擎 , 之 前 所 有 的 操 作 虽 然 说 是 在 ClickHouse 集群中进行的,但是实际上是在 node1 节点中单独操作的,与 node2、node3 无关,使用分布式引擎声明的表才可以在其他节点访问与操作。
Distributed 引擎和 Merge 引擎类似,本身不存放数据,功能是在不同的 server 上把多张相同结构的物理表合并为一张逻辑表。
分布式引擎语法:
Distributed(cluster_name, database_name, table_name[, sharding_key])
对以上语法解释:
cluster_name:集群名称,与集群配置中的自定义名称相对应。配置在
/etc/metrika.xml 文件中,如下图:
目录文件有的在:[root@dc-o-ch-08 dev]# cat /etc/clickhouse-server/metrika.xml
database_name:数据库名称。
table_name:表名称。
sharding_key:可选的,用于分片的 key 值,在数据写入的过程中,分布式表
会依据分片 key 的规则,将数据分布到各个节点的本地表。
注意:创建分布式表是读时检查的机制,也就是说对创建分布式表和本地表的顺序并没
有强制要求。
例子:
#使用默认的 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
#在 node1 上创建分布式表 t_distributed,表引擎使用 Distributed 引擎
node1 :)
create table t_distributed(id UInt8,name String) engine = Distributed(clickhouse_cluster_3shards_1replicas,default,test_local,id);
注意:以上分布式表 t_distributed 只存在与 node1
#分别在 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,'赵八');
#查询分布式表 t_distributed 中的数据
node1 :) select * from t_distributed;
#向分布式表 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 节点上创建的分布式表 t_distributed 虽然数据是分布式存储在每 个 clickhouse 节点上的,但是只能在 node1 上查询 t_distributed 表,其 他 clickhouse 节点查询不到此分布式表。如果想要在每台 clickhouse 节点上都能访问分 布式表我们可以指定集群,创建分布式表:
#创建分布式表 t_cluster ,引擎使用 Distributed 引擎
create table t_cluster on cluster clickhouse_cluster_3shards_1replicas (id UInt8,name String) engine =Distributed(clickhouse_cluster_3shards_1replicas,default,test_local,id);
中使用了 ON CLUSTER 分布式 DDL(数据库定义语言),这意味着在集群 的每个分片节点上,都会创建一张 Distributed 表,这样便可以从其中任意一端发起对 所有分片的读、写请求。
MergeTree系列表引擎
所有的表引擎中,最为核心的当属 MergeTree 系列表引擎,这些表引擎拥有最为强 大的性能和最广泛的使用场合。对于非 MergeTree 系列的其他引擎而言,主要用于特殊用 途,场景相对有限。而 MergeTree 系列表引擎是官方主推的存储引擎,有主键索引、数据 分区、数据副本、数据采样、删除和修改等功能,支持几乎所有 ClickHouse 核心功能。MergeTree 系 列 表 引 擎 包 含 : MergeTree 、 ReplacingMergeTree 、
SummingMergeTree(汇总求和功能)、AggregatingMergeTree(聚合功能)、
CollapsingMergeTree(折叠删除功能)、VersionedCollapsingMergeTree(版本折叠功能)引擎,在这些的基础上还可以叠加 Replicated 和 Distributed。MergeTree 在写入一批数据时,数据总会以数据片段的形式写入磁盘,且数据片段在磁盘上不可修改。为了避免片段过多,ClickHouse 会通过后台线程,定期合并这些数据片段,属于相同分区的数据片段会被合成一个新的片段。这种数据片段往复合并的特点,也 正是合并树名称的由来。
MergeTree 作为家族系列最基础的表引擎,主要有以下特点:
存储的数据按照主键排序:创建稀疏索引加快数据查询速度。
支持数据分区,可以通过 PARTITION BY 语句指定分区字段。
支持数据副本。
支持数据采样
建表语句:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] ( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2], ... INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1, INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2 ) ENGINE = MergeTree() ORDER BY expr [PARTITION BY expr] [PRIMARY KEY expr] [SAMPLE BY expr] [TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...] [SETTINGS name=value, ...]
关于以上建表语句的解释如下:
ENGINE:ENGINE = MergeTree(),MergeTree 引擎没有参数。
ORDER BY:排序字段。比如 ORDER BY (Col1, Col2),值得注意的是,如果
没有使用 PRIMARY KEY 显式的指定主键 ORDER BY 排序字段自动作为主键。如
果不需要排序,则可以使用 ORDER BY tuple() 语法,这样的话,创建的表也
就不包含主键。这种情况下,ClickHouse 会按照插入的顺序存储数据。必选项。
PARTITION BY : 分 区 字 段 , 例 如 要 按 月 分 区 , 可 以 使 用 表 达 式
toYYYYMM(date_column),这里的 date_column 是一个 Date 类型的列,分
区名的格式会是"YYYYMM"。可选。
PRIMARY KEY:指定主键,如果排序字段与主键不一致,可以单独指定主键字段。
否则默认主键是排序字段。大部分情况下不需要再专门指定一个 PRIMARY KEY
子句,注意,在 MergeTree 中主键并不用于去重,而是用于索引,加快查询速度。
可选。
另外,如果指定了 PRIMARY KEY 与排序字段不一致,要保证 PRIMARY KEY 指
定的主键是 ORDER BY 指定字段的前缀,比如:
这种强制约束保障了即便在两者定义不同的情况下,主键仍然是排序键的前缀,不
会出现索引与数据顺序混乱的问题。
--允许 ... ... ORDER BY (A,B,C) PRIMARY KEY A --报错 ... ... ORDER BY (A,B,C) PRIMARY KEY B DB::Exception: Primary key must be a prefix of the sorting key