在上一篇分享中,我们介绍了ClickHouse的安装部署和简单使用。本文将介绍ClickHouse中一个非常重要的概念—表引擎(table engine)。如果对MySQL熟悉的话,或许你应该听说过InnoDB和MyISAM存储引擎。不同的存储引擎提供不同的存储机制、索引方式、锁定水平等功能,也可以称之为表类型。ClickHouse提供了丰富的表引擎,这些不同的表引擎也代表着不同的表类型。比如数据表拥有何种特性、数据以何种形式被存储以及如何被加载。本文会对ClickHouse中常见的表引擎进行介绍,主要包括以下内容:
- 表引擎的作用是什么
- MergeTree系列引擎
- Log家族系列引擎
- 外部集成表引擎
- 其他特殊的表引擎
温馨提示:本文内容较长,建议收藏
表引擎的作用是什么
- 决定表存储在哪里以及以何种方式存储
- 支持哪些查询以及如何支持
- 并发数据访问
- 索引的使用
- 是否可以执行多线程请求
- 数据复制参数
表引擎分类
引擎分类 | 引擎名称 |
---|---|
MergeTree系列 | MergeTree 、ReplacingMergeTree 、SummingMergeTree 、 AggregatingMergeTree CollapsingMergeTree 、 VersionedCollapsingMergeTree 、GraphiteMergeTree |
Log系列 | TinyLog 、StripeLog 、Log |
Integration Engines | Kafka 、MySQL、ODBC 、JDBC、HDFS |
Special Engines | Distributed 、MaterializedView、 Dictionary 、Merge 、File、Null 、Set 、Join 、 URL View、Memory 、 Buffer |
Log系列表引擎
应用场景
Log系列表引擎功能相对简单,主要用于快速写入小表(1百万行左右的表),然后全部读出的场景。即一次写入多次查询。
Log系列表引擎的特点
共性特点
- 数据存储在磁盘上
- 当写数据时,将数据追加到文件的末尾
- 不支持并发读写,当向表中写入数据时,针对这张表的查询会被阻塞,直至写入动作结束
- 不支持索引
- 不支持原子写:如果某些操作(异常的服务器关闭)中断了写操作,则可能会获得带有损坏数据的表
- 不支持ALTER操作(这些操作会修改表设置或数据,比如delete、update等等)
区别
-
TinyLog
TinyLog是Log系列引擎中功能简单、性能较低的引擎。它的存储结构由数据文件和元数据两部分组成。其中,数据文件是按列独立存储的,也就是说每一个列字段都对应一个文件。除此之外,TinyLog不支持并发数据读取。
-
StripLog支持并发读取数据文件,当读取数据时,ClickHouse会使用多线程进行读取,每个线程处理一个单独的数据块。另外,StripLog将所有列数据存储在同一个文件中,减少了文件的使用数量。
-
Log支持并发读取数据文件,当读取数据时,ClickHouse会使用多线程进行读取,每个线程处理一个单独的数据块。Log引擎会将每个列数据单独存储在一个独立文件中。
TinyLog表引擎使用
该引擎适用于一次写入,多次读取的场景。对于处理小批数据的中间表可以使用该引擎。值得注意的是,使用大量的小表存储数据,性能会很低。
CREATE TABLE emp_tinylog (
emp_id UInt16 COMMENT '员工id',
name String COMMENT '员工姓名',
work_place String COMMENT '工作地点',
age UInt8 COMMENT '员工年龄',
depart String COMMENT '部门',
salary Decimal32(2) COMMENT '工资'
)ENGINE=TinyLog();
INSERT INTO emp_tinylog
VALUES (1,'tom','上海',25,'技术部',20000),(2,'jack','上海',26,'人事部',10000);
INSERT INTO emp_tinylog
VALUES (3,'bob','北京',33,'财务部',50000),(4,'tony','杭州',28,'销售事部',50000);
进入默认数据存储目录,查看底层数据存储形式,可以看出:TinyLog引擎表每一列都对应的文件
[root@cdh04 emp_tinylog]# pwd
/var/lib/clickhouse/data/default/emp_tinylog
[root@cdh04 emp_tinylog]# ll
总用量 28
-rw-r----- 1 clickhouse clickhouse 56 9月 17 14:33 age.bin
-rw-r----- 1 clickhouse clickhouse 97 9月 17 14:33 depart.bin
-rw-r----- 1 clickhouse clickhouse 60 9月 17 14:33 emp_id.bin
-rw-r----- 1 clickhouse clickhouse 70 9月 17 14:33 name.bin
-rw-r----- 1 clickhouse clickhouse 68 9月 17 14:33 salary.bin
-rw-r----- 1 clickhouse clickhouse 185 9月 17 14:33 sizes.json
-rw-r----- 1 clickhouse clickhouse 80 9月 17 14:33 work_place.bin
## 查看sizes.json数据
## 在sizes.json文件内使用JSON格式记录了每个.bin文件内对应的数据大小的信息
{
"yandex":{
"age%2Ebin":{
"size":"56"
},
"depart%2Ebin":{
"size":"97"
},
"emp_id%2Ebin":{
"size":"60"
},
"name%2Ebin":{
"size":"70"
},
"salary%2Ebin":{
"size":"68"
},
"work_place%2Ebin":{
"size":"80"
}
}
}
当我们执行ALTER操作时会报错,说明该表引擎不支持ALTER操作
-- 以下操作会报错:
-- DB::Exception: Mutations are not supported by storage TinyLog.
ALTER TABLE emp_tinylog DELETE WHERE emp_id = 5;
ALTER TABLE emp_tinylog UPDATE age = 30 WHERE emp_id = 4;
StripLog表引擎使用
相比TinyLog而言,StripeLog拥有更高的查询性能(拥有.mrk标记文件,支持并行查询),同时其使用了更少的文件描述符(所有数据使用同一个文件保存)。
CREATE TABLE emp_stripelog (
emp_id UInt16 COMMENT '员工id',
name String COMMENT '员工姓名',
work_place String COMMENT '工作地点',
age UInt8 COMMENT '员工年龄',
depart String COMMENT '部门',
salary Decimal32(2) COMMENT '工资'
)ENGINE=StripeLog;
-- 插入数据
INSERT INTO emp_stripelog
VALUES (1,'tom','上海',25,'技术部',20000),(2,'jack','上海',26,'人事部',10000);
INSERT INTO emp_stripelog
VALUES (3,'bob','北京',33,'财务部',50000),(4,'tony','杭州',28,'销售事部',50000);
-- 查询数据
-- 由于是分两次插入数据,所以查询时会有两个数据块
cdh04 :) select * from emp_stripelog;
SELECT *
FROM emp_stripelog
┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐
│ 1 │ tom │ 上海 │ 25 │ 技术部 │ 20000.00 │
│ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │
└────────┴──────┴────────────┴─────┴────────┴──────────┘
┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐
│ 3 │ bob │ 北京 │ 33 │ 财务部 │ 50000.00 │
│ 4 │ tony │ 杭州 │ 28 │ 销售事部 │ 50000.00 │
└────────┴──────┴────────────┴─────┴──────────┴──────────┘
进入默认数据存储目录,查看底层数据存储形式
[root@cdh04 emp_stripelog]# pwd
/var/lib/clickhouse/data/default/emp_stripelog
[root@cdh04 emp_stripelog]# ll
总用量 12
-rw-r----- 1 clickhouse clickhouse 673 9月 17 15:11 data.bin
-rw-r----- 1 clickhouse clickhouse 281 9月 17 15:11 index.mrk
-rw-r----- 1 clickhouse clickhouse 69 9月 17 15:11 sizes.json
可以看出StripeLog表引擎对应的存储结构包括三个文件:
- data.bin:数据文件,所有的列字段使用同一个文件保存,它们的数据都会被写入data.bin。
- index.mrk:数据标记,保存了数据在data.bin文件中的位置信息(每个插入数据块对应列的offset),利用数据标记能够使用多个线程,以并行的方式读取data.bin内的压缩数据块,从而提升数据查询的性能。
- sizes.json:元数据文件,记录了data.bin和index.mrk大小的信息
提示:
StripeLog
引擎将所有数据都存储在了一个文件中,对于每次的INSERT操作,ClickHouse会将数据块追加到表文件的末尾StripeLog引擎同样不支持
ALTER UPDATE
和ALTER DELETE
操作
Log表引擎使用
Log引擎表适用于临时数据,一次性写入、测试场景。Log引擎结合了TinyLog表引擎和StripeLog表引擎的长处,是Log系列引擎中性能最高的表引擎。
CREATE TABLE emp_log (
emp_id UInt16 COMMENT '员工id',
name String COMMENT '员工姓名',
work_place String COMMENT '工作地点',
age UInt8 COMMENT '员工年龄',
depart String COMMENT '部门',
salary Decimal32(2) COMMENT '工资'
)ENGINE=Log;
INSERT INTO emp_log VALUES (1,'tom','上海',25,'技术部',20000),(2,'jack','上海',26,'人事部',10000);
INSERT INTO emp_log VALUES (3,'bob','北京',33,'财务部',50000),(4,'tony','杭州',28,'销售事部',50000);
-- 查询数据,
-- 由于是分两次插入数据,所以查询时会有两个数据块
cdh04 :) select * from emp_log;
SELECT *
FROM emp_log
┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart─┬───salary─┐
│ 1 │ tom │ 上海 │ 25 │ 技术部 │ 20000.00 │
│ 2 │ jack │ 上海 │ 26 │ 人事部 │ 10000.00 │
└────────┴──────┴────────────┴─────┴────────┴──────────┘
┌─emp_id─┬─name─┬─work_place─┬─age─┬─depart───┬───salary─┐
│ 3 │ bob │ 北京 │ 33 │ 财务部 │ 50000.00 │
│ 4 │ tony │ 杭州 │ 28 │ 销售事部 │ 50000.00 │
└────────┴──────┴────────────┴─────┴──────────┴──────────┘
进入默认数据存储目录,查看底层数据存储形式
[root@cdh04 emp_log]# pwd
/var/lib/clickhouse/data/default/emp_log
[root@cdh04 emp_log]# ll
总用量 32
-rw-r----- 1 clickhouse clickhouse 56 9月 17 15:55 age.bin
-rw-r----- 1 clickhouse clickhouse 97 9月 17 15:55 depart.bin
-rw-r----- 1 clickhouse clickhouse 60 9月 17 15:55 emp_id.bin
-rw-r----- 1 clickhouse clickhouse 192 9月 17 15:55 __marks.mrk
-rw-r----- 1 clickhouse clickhouse 70 9月 17 15:55 name.bin
-rw-r----- 1 clickhouse clickhouse 68 9月 17 15:55 salary.bin
-rw-r----- 1 clickhouse clickhouse 216 9月 17 15:55 sizes.json
-rw-r----- 1 clickhouse clickhouse 80 9月 17 15:55 work_place.bin
Log引擎的存储结构包含三部分:
- 列.bin:数据文件,数据文件按列单独存储
- __marks.mrk:数据标记,统一保存了数据在各个.bin文件中的位置信息。利用数据标记能够使用多个线程,以并行的方式读取。.bin内的压缩数据块,从而提升数据查询的性能。
- sizes.json:记录了.bin和__marks.mrk大小的信息
提示:
Log表引擎会将每一列都存在一个文件中,对于每一次的INSERT操作,都会对应一个数据块
MergeTree系列引擎
在所有的表引擎中,最为核心的当属MergeTree系列表引擎,这些表引擎拥有最为强大的性能和最广泛的使用场合。对于非MergeTree系列的其他引擎而言,主要用于特殊用途,场景相对有限。而MergeTree系列表引擎是官方主推的存储引擎,支持几乎所有ClickHouse核心功能。
MergeTree表引擎
MergeTree在写入一批数据时,数据总会以数据片段的形式写入磁盘,且数据片段不可修改。为了避免片段过多,ClickHouse会通过后台线程,定期合并这些数据片段,属于相同分区的数据片段会被合成一个新的片段。这种数据片段往复合并的特点,也正是合并树名称的由来。
MergeTree作为家族系列最基础的表引擎,主要有以下特点:
- 存储的数据按照主键排序:允许创建稀疏索引,从而加快数据查询速度
- 支持分区,可以通过PRIMARY KEY语句指定分区字段。
- 支持数据副本
- 支持数据采样
建表语法
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),值得注意的是,如果没有指定主键,默认情况下 sorting key(排序字段)即为主键。如果不需要排序,则可以使用**ORDER BY tuple()**语法,这样的话,创建的表也就不包含主键。这种情况下,ClickHouse会按照插入的顺序存储数据。必选。
- PARTITION BY:分区字段,可选。
- PRIMARY KEY:指定主键,如果排序字段与主键不一致,可以单独指定主键字段。否则默认主键是排序字段。可选。
- SAMPLE BY:采样字段,如果指定了该字段,那么主键中也必须包含该字段。比如
SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))
。可选。 - TTL:数据的存活时间。在MergeTree中,可以为某个列字段或整张表设置TTL。当时间到达时,如果是列字段级别的TTL,则会删除这一列的数据;如果是表级别的TTL,则会删除整张表的数据。可选。
- SETTINGS:额外的参数配置。可选。
建表示例
CREATE TABLE emp_mergetree (
emp_id UInt16 COMMENT '员工id',
name String COMMENT '员工姓名',
work_place String COMMENT '工作地点',
age UInt8 COMMENT '员工年龄',
depart String COMMENT '部门',
salary Decimal32(2) COMMENT '工资'
)ENGINE=MergeTree()
ORDER BY emp_id
PARTITION BY work_place
;
-- 插入数据
INSERT INTO emp_mergetree
VALUES (1,'tom','上海',25,'技术部',20000),(2,'jack','上海',26,'人事部',10000);
INSERT INTO emp_mergetree
VALUES