15.1 ClickHouse介绍
官方文档介绍:ClickHouse Docs
15.1.1 ClickHouse简介
-
介绍
-
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.新浪微博用于用户行为数据记录和分析工作。
3.用于广告网络和RTB,电子商务的用户行为分析。
4.信息安全里面的日志分析。
5.检测和遥感信息的挖掘。
6.商业智能。
7.网络游戏以及物联网的数据处理和价值数据分析。
8.最大的应用来自于Yandex的统计分析服务Yandex.Metrica,类似于谷歌Analytics(GA),或友盟统计,小米统计,帮助网站或移动应用进行数据分析和精细化运营工具,据称Yandex.Metrica为世界上第二大的网站分析平台。ClickHouse在这个应用中,部署了近四百台机器,每天支持200亿的事件和历史总记录超过13万亿条记录,这些记录都存有原始数据(非聚合数据),随时可以使用SQL查询和分析,生成用户报告。
15.1.2 特点
-
列式存储
-
对于列的聚合、计数、求和等统计操作优于行式存储
-
由于某一列的数据类型都是相同的,针对于数据存储更容易进行数据压缩,每一列选择更优的数据压缩算法,大大提高了数据的压缩比重
-
数据压缩比更好,一方面节省了磁盘空间,另一方面对于cache也有了更大的发挥空间
-
列式存储不支持事务
-
-
DBMS功能
-
几乎覆盖了标准 SQL 的大部分语法,包括 DDL 和 DML、,以及配套的各种函数;用户管理及权限管理、数据的备份与恢复
-
-
多样化引擎:
-
目前包括合并树、日志、接口和其他四大类20多种引擎。
-
-
高吞吐写入能力:
-
ClickHouse采用类LSM Tree的结构,数据写入后定期在后台Compaction。通过类 LSM tree的结构, ClickHouse在数据导入时全部是顺序append写,写入后数据段不可更改,在后台compaction时也是多个段merge sort后顺序写回磁盘。
-
顺序写的特性,充分利用了磁盘的吞吐能力。
-
-
数据分区与线程及并行:
-
ClickHouse将数据划分为多个partition,每个partition再进一步划分为多个index granularity(索引粒度),然后通过多个CPU核心分别处理其中的一部分来实现并行数据处理。
-
在这种设计下, 单条 Query 就能利用整机所有 CPU。 极致的并行处理能力,极大的降低了查询延时。
-
所以, ClickHouse 即使对于大量数据的查询也能够化整为零平行处理。但是有一个弊端就是对于单条查询使用多cpu,就不利于同时并发多条查询。所以对于高 qps 的查询业务并不是强项。
-
-
ClickHouse 像很多 OLAP 数据库一样,单表查询速度优于关联查询,而且 ClickHouse的两者差距更为明显。
关联查询:clickhouse会将右表加载到内存
15.1.3 clickhouse查询速度快的原因
-
C++可以利用硬件优势 ,连续IO,提高了磁盘驱动器的效率
-
摒弃了Hadoop生态
-
数据底层以列式存储
-
利用单节点的多核并行处理,向量化引擎与SIMD提高了CPU利用率,多核多节点并行化大查询
-
为数据建立索引一级、二级、稀疏索引
-
使用大量的算法处理数据
-
支持向量化处理
-
预先设计运算模型-预先计算
-
分布式处理数据
15.1.4 优点
-
面向列的DBMS
-
ClickHouse是一个DBMS,而不是一个单一的数据库。它允许在运行时创建表和数据库、加载数据和运行查询,而无需重新配置和重新启动服务器
-
-
数据压缩
-
一些面向列的DBMS(INFINIDB CE 和 MonetDB)不使用数据压缩。但是,数据压缩 确实是提高了性能
-
-
磁盘存储的数据
-
许多面向列的DBMS(SPA HANA和GooglePowerDrill))只能在内存中工作。但即使在数千台服务器上,内存也太小了
-
-
多核并行处理
-
多核多节点并行化大型查询
-
-
多个服务器上分布式处理
-
在clickhouse中,数据可以驻留在不同的分片上。每个分片都可以用于容错的一组副本,查询会在所有分片上并行处理
-
-
SQL支持
-
ClickHouse sql 跟真正的sql有不一样的函数名称。不过语法基本跟SQL语法兼容,支持JOIN/FROM/IN 和JOIN子句及标量子查询支持子查询
-
-
向量化引擎
-
数据不仅按列式存储,而且由矢量-列的部分进行处理,这使得开发者能够实现高CPU 性能
-
-
实时数据更新
-
ClickHouse支持主键表。为了快速执行对主键范围的查询,数据使用合并树(MergeTree)进行递增排序。由于这个原因,数据可以不断地添加到表中
-
-
支持近似计算
-
统计全国到底有多少人?143456754 14.3E
-
-
数据复制和数据完整性的支持
-
ClickHouse使用异步多主复制。写入任何可用的复本后,数据将分发到所有剩余的副本。系统在不同的副本上保持相同的数据。数据在失败后自动恢复
-
15.1.5 缺点
-
没有完整的事务支持
-
缺少完整Update/Delete操作,缺少高频率、低延迟的修改或删除已存在数据的能力,仅用于批量删除或修改数据。
-
聚合结果必须小于一台机器的内存大小
-
支持有限操作系统,正在慢慢完善
-
不适合Key-value存储,不支持Blob等文档型数据库
15.2 系统架构
15.2.1 Column和Field
-
理解
-
Column和Field是ClickHouse数据最基础的映射单元,内存中的一列数据由一个Column对象表示
-
column 是一列数据
-
field 列中某一个值
-
-
column
15.2.2 DAtaType 数据类型
-
理解
-
描述了列的数据类型
-
-
作用
-
负责序列化和反序列化,读写二进制或文本形式的列或单个值构成的块
-
DataType虽然负责序列化相关工作,但它并不直接负责数据的读取,而是转由从Column或Field对象获取
-
-
15.2.3 Block
-
理解
-
由column+datatype+列名 构成的集合
-
表的操作对象是Block,并且采用了流的形式
-
15.2.4 BlockStream
-
理解
-
负责块数据的读取和写出(处理数据)
-
-
作用
-
读或写一个表
-
完成数据格式化
-
执行数据转换
-
15.2.5 Format
-
理解
-
向客户端展示数据的方式
-
15.2.6 数据读写IO
-
理解
-
有一个缓冲区负责数据的读写
-
对于面向字节的输入输出,有 ReadBuffer 和 WriteBuffer 这两个抽象类。
-
15.2.7 数据表table
-
理解
-
多个列的一个集合体
-
读取数据的时候以表为单位进行操作,操作的时候以BlockStream操作Block
-
15.2.8 解析器Parser
-
理解
-
对SQL语句进行解析
-
-
作用
-
解析器创建AST对象
-
15.2.9 解释器Interpreter
-
理解
-
解析SQL语句
-
Parser分析器可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。不同的SQL语句,会经由不同的Parser实现类解析
-
-
作用
-
解释器负责解释AST,并进一步创建查询的执行管道
-
15.2.10 函数Functions
-
理解
-
分类
-
单行函数
-
普通函数不会改变行数 - 它们的执行看起来就像是独立地处理每一行数据
-
-
组函数(聚合函数)
-
15.2.11 Cluster与Replication
-
理解
-
向客户端展示数据的方式
-
ClickHouse的集群由分片 ( Shard ) 组成,而每个分片又通过副本 ( Replica ) 组成
-
15.3 环境搭建
15.4 数据定义
15.4.1 数据类型
1. 基本数据类型
-
整数Int8、Int16、Int32 和 Int64
-
浮点数 Float32 和 Float64
-
定点数 Decimal32、Decimal64 和Decimal128
-
布尔 Ulnt8 限制值为0或1
2. 字符串
-
String、FixedString 和 UUID
-
String不限制长度,相当于Varchar、Text、Clob和Blob等字符类型
-
FixedString(N)相当于Char,长度固定,数据长度不够时,添加空字节(null);长度过长返回错误消息
-
UUID:32位,格式8-4-4-4-12,如果未被赋值,则用0填充
-
-
创建一个表
CREATE TABLE UUID_TEST ( c1 UUID, c2 String ) ENGINE = Memory; --第一行UUID有值 INSERT INTO UUID_TEST SELECT generateUUIDv4(),'t1' --第二行UUID没有值 INSERT INTO UUID_TEST(c2) VALUES('t2')
3. 日期时间
-
Date: 2020-02-02 精确到天
CREATE TABLE Date_TEST ( c1 Date ) ENGINE = Memory --以字符串形式写入 INSERT INTO Date_TEST VALUES('2019-06-22') SELECT c1, toTypeName(c1) FROM Date_TEST
-
DateTime: 2020-02-02 20:20:20 精确到秒
CREATE TABLE Datetime_TEST ( c1 Datetime ) ENGINE = Memory --以字符串形式写入 INSERT INTO Datetime_TEST VALUES('2019-06-22 00:00:00') SELECT c1, toTypeName(c1) FROM Datetime_TEST
-
DateTime64: 2020-02-02 20:20:20.335 精确到亚秒,可以设置精度
CREATE TABLE Datetime64_TEST ( c1 Datetime64(2) ) ENGINE = Memory --以字符串形式写入 INSERT INTO Datetime64_TEST VALUES('2019-06-22 00:00:00') SELECT c1, toTypeName(c1) FROM Datetime64_TEST
4. 复合类型
-
数组
-
创建数据:array(T)或 [ ] ,类型必须相同
SELECT array(1, 2) as a , toTypeName(a) SELECT [1, 2, null] as a , toTypeName(a) CREATE TABLE Array_TEST ( c1 Array(String) ) engine = Memory
-
-
元组
-
由多个元素组 成,允许不同类型
-
创建数据:(T1, T2, …),Tuple(T1, T2, …)
SELECT tuple(1,'a',now()) AS x, toTypeName(x) SELECT (1,2.0,null) AS x, toTypeName(x) CREATE TABLE Tuple_TEST ( c1 Tuple(String,Int8) ) ENGINE = Memory;
-
-
枚举类型
-
ClickHouse提供了Enum8和Enum16两种枚举类型,它们除了取值范围不同之外,别无二致。
-
枚举固定使用(String:Int)Key/Value键值对的形式定义数据,所以Enum8和Enum16分别会对应(String:Int8)和(String:Int16)
-
用(String:Int) Key/Value键值对的形式定义数据,键值对不能同时为空,不允许重复,key允许为空字符串,需要看到对应的值进行转换
CREATE TABLE Enum_TEST ( c1 Enum8('ready' = 1, 'start' = 2, 'success' = 3, 'error' = 4) ) ENGINE = Memory; --正确语句 INSERT INTO Enum_TEST VALUES('ready'); INSERT INTO Enum_TEST VALUES('start'); --错误语句 INSERT INTO Enum_TEST VALUES('stop');
-
-
嵌套类型
-
Nested(Name1 Type1,Name2 Type2,…)
-
相当于表中嵌套一张表,插入时相当于一个多维数组的格式,一个字段对应一个数组
CREATE TABLE nested_test ( name String, age UInt8 , dept Nested( id UInt8, name String ) ) ENGINE = Memory; --行与行之间,数组长度无须对齐 INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001,10002], ['研发部','技术支持中心','测试部']); INSERT INTO nested_test VALUES ('bruce' , 30 , [10000,10001], ['研发部','技术支持中心']);
-
5. 其他类型
-
Nullable(TypeName)
-
只能与基础数据类型搭配使用,表示某个类型的值可以为NULL;NULLable(lnt8)表示可以存储Int8类型的值,没有值时存NULL
-
注意
-
不能与复合类型数据一起使用、
-
不能作为索引字段
-
尽量避免使用,字段被Nullable修饰后会额外生成[Column].null.bin 文件保存Null值,增加开销
-
CREATE TABLE Null_TEST ( c1 String, c2 Nullable(UInt8) ) ENGINE = TinyLog; --通过Nullable修饰后c2字段可以被写入Null值: INSERT INTO Null_TEST VALUES ('nauu',null) INSERT INTO Null_TEST VALUES ('bruce',20) SELECT c1 , c2 ,toTypeName(c2) FROM Null_TEST
-
-
Domain
-
IPv4 使用 UInt32 存储。如 116.253.40.133
-
IPv6 使用 FixedString(16) 存储。如 2a02:aa08:e000:3100::2
CREATE TABLE IP4_TEST ( url String, ip IPv4 ) ENGINE = Memory; INSERT INTO IP4_TEST VALUES ('www.nauu.com','192.0.0.0') SELECT url , ip ,toTypeName(ip) FROM IP4_TEST
-
15.4.2 数据库
-
理解
-
数据起到了命名空间的作用,可以有效规避命名空间冲突的问题,也为后续的数据隔离提供了支撑,任何一张数据表,都必须归属在某个数据库之下
-
-
操作语法
CREATE DATABASE IF NOT EXISTS db_name [ENGINE = engine] SHOW DATABASES DROP DATABASE [IF EXISTS] db_name
-
数据库引擎
-
Ordinary :默认引擎
-
在绝大多数情况下我们都会使用默认引擎,使用时无须刻意声明。在此数据库下可以使用任意类型的表引擎
-
-
Dictionary :字典引擎
-
此类数据库会自动为所有数据字典创建它们的数据表
-
-
Memory : 内存引擎
-
用于存放临时数据。此类数据库下的数据表只会停留在内存中,不会涉及任何磁盘操作,当服务重启后数据会被清除。
-
-
Lazy : 日志引擎
-
此类数据库下只能使用Log系列的表引擎
-
-
MySQL:MySQL引擎
-
此类数据库下会自动拉取远端MySQL中的数据,并为它们创建MySQL表引擎的数据表
-
-
15.4.3 数据表
1. 创建表
ClickHouse目前提供了三种最基本的建表方法
-
第一种是常规定义方法
-
使用[db_name.]参数可以为数据表指定数据库,如果不指定此参数,则默认会使用default数据库
CREATE TABLE [IF NOT EXISTS] [db_name.]table_name ( name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr], name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr], 省略… ) ENGINE = engine CREATE TABLE hits_v1 ( Title String, URL String , EventTime DateTime ) ENGINE = Memory;
-
-
第二种定义方法是复制其他表的结构
-
支持在不同的数据库之间复制表结构
CREATE TABLE [IF NOT EXISTS] [db_name1.]table_name AS [db_name2.] table_name2 [ENGINE = engine] --创建新的数据库 CREATE DATABASE IF NOT EXISTS new_db --将default.hits_v1的结构复制到new_db.hits_v1 CREATE TABLE IF NOT EXISTS new_db.hits_v1 AS default.hits_v1 ENGINE = TinyLog
-
-
第三种定义方法是通过select字句的形式创建
-
根据select字句建立相应的表结构,同时还会将select字句查询的数据顺带写入
CREATE TABLE [IF NOT EXISTS] [db_name.]table_name ENGINE = engine AS SELECT … CREATE TABLE IF NOT EXISTS hits_v1_1 ENGINE = Memory AS SELECT * FROM hits_v1
-
2. 删除表
-
理解
-
ClickHouse和大多数数据库一样,使用DESC查询可以返回数据表的定义结构
-
-
删除表的DROP语句
DROP TABLE [IF EXISTS] [db_name.]table_name
3. 临时表
-
理解
-
ClickHouse也有临时表的概念,创建临时表的方法是普通表的基础之上添加temporary关键字
CREATE TEMPORARY TABLE [IF NOT EXISTS] table_name ( name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr], name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr], )
-
-
特点
-
它的生命周期是会话绑定的,所以它只支持Memory表引擎,如果会话结束,数据表就会被销毁
-
临时表不属于任何数据库,所以在它的建表语句中,既没有数据库参数也没有表引擎参数
-
临时表的优先级是大于普通表的,当两张数据表名称相同的时候,会优先读取临时表的数据
-
4. 分区表
-
区分数据分区(partition)和数据分片(shard)
-
数据分区是针对本地数据而言的,是数据的一种纵向切分
-
数据分片则与 ClickHouse 集群相关,我们目前都是单机的,所以不涉及数据分片
-
-
案例
CREATE TABLE partition_v1 ( ID String, URL String, EventTime Date ) ENGINE = MergeTree() PARTITION BY toYYYYMM(EventTime) ORDER BY ID INSERT INTO partition_v1 VALUES ('A000','www.nauu.com', '2019-05-01'), ('A001','www.brunce.com', '2019-06-02') SELECT table,partition,path from system.parts WHERE table = 'partition_v1'
5. 数据表操作
-
追加新字段
ALTER TABLE tb_name ADD COLUMN [IF NOT EXISTS] name [type] [default_expr] [AFTER name_after] ALTER TABLE testcol_v1 ADD COLUMN OS String DEFAULT 'mac' ALTER TABLE testcol_v1 ADD COLUMN IP String AFTER ID
-
修改字段类型
ALTER TABLE tb_name MODIFY COLUMN [IF EXISTS] name [type] [default_expr] ALTER TABLE testcol_v1 MODIFY COLUMN IP IPv4
-
修改备注
ALTER TABLE tb_name COMMENT COLUMN [IF EXISTS] name 'some comment' ALTER TABLE testcol_v1 COMMENT COLUMN ID '主键ID' DESC testcol_v1
-
删除已有字段
ALTER TABLE tb_name DROP COLUMN [IF EXISTS] name ALTER TABLE testcol_v1 DROP COLUMN URL
-
清空数据表
TRUNCATE TABLE [IF EXISTS] [db_name.]tb_name TRUNCATE TABLE db_test.testcol_v2
15.4.4 视图
ClickHouse拥有普通和物化两种视图,其中物化视图拥有独立的存储,而普通视图只是一层简单的查询代理
-
普通视图
-
普通视图不会存储任何数据,它只是一层单纯的select查询映射,起着简化查询、明晰语义的作用,对查询性能不会有任何增强
CREATE VIEW [IF NOT EXISTS] [db_name.]view_name AS SELECT ...
-
-
物化视图
CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]table_name [TO[db.]name] [ENGINE = engine] [POPULATE] AS SELECT
-
物化视图支持表引擎,数据存储形式由它的表引擎决定
-
物化视图创建好之后,如果源表被写入新数据,那么物化视图也会同步更新
-
POPULATE修饰符决定了物化视图的初始化策略:
-
如果使用了POPULATE修饰符,那么在创建视图的过程中,会连带将源表中已存在的数据一并导入,如同执行了SELECT INTO一般;
-
反之,如果不使用POPULATE修饰符,那么物化视图在创建之后是没有数据的,它只会同步在此之后被写入源表的数据。
-
物化视图目前并不支持同步删除,如果在源表中删除了数据,物化视图的数据仍会保 留。
-
-
15.4.5 数据的CRUD
1. 数据的写入
INSERT语句支持三种语法范式,三种范式各有不同,可以根据写入的需求灵活运用。
-
第一种是使用VALUES格式的常规语法:
INSERT INTO [db.]table [(c1, c2, c3…)] VALUES (v11, v12, v13…), (v21, v22, v23…), ... INSERT INTO partition_v2 VALUES ('A0011','www.nauu.com', '2019-10-01'), ('A0012','www.nauu.com', '2019-11-20') INSERT INTO partition_v2 VALUES ('A0014',toString(1+2), now())
-
第二种是使用指定格式的语法:
INSERT INTO [db.]table [(c1, c2, c3…)] FORMAT format_name data_set INSERT INTO partition_v2 FORMAT CSV \ 'A0017','www.nauu.com', '2019-10-01' \ 'A0018','www.nauu.com', '2019-10-01'
-
第三种是使用SELECT子句形式的语法:
INSERT INTO [db.]table [(c1, c2, c3…)] SELECT ... INSERT INTO partition_v2 SELECT * FROM partition_v1 INSERT INTO partition_v2 SELECT 'A0020', 'www.jack.com', now()
2. 数据的删除和修改
-
理解
-
ClickHouse提供了DELETE和UPDATE的能力,这类操作被称为Mutation查询,它可以看作ALTER语句的变种。
-
虽然Mutation能最终实现修改和删除,但不能完全以通常意义上的UPDATE和DELETE来理解,我们必须清醒地认识到它的不同:
-
首先,Mutation语句是一种“很重”的操作,更适用于批量数据的修改和删除;
-
其次,它不支持事务,一旦语句被提交执行,就会立刻对现有数据产生影响,无法回滚;
-
最后,Mutation语句的执行是一个异步的后台过程,语句被提交之后就会立即返回。
-
-
-
DELETE语句的完整语法
ALTER TABLE [db_name.]table_name DELETE WHERE filter_expr ALTER TABLE partition_v2 DELETE WHERE ID = 'A003'
-
UPDATE语句的完整语法
ALTER TABLE [db_name.]table_name UPDATE column1 = expr1 [, ...] WHERE filter_expr ALTER TABLE partition_v2 UPDATE URL = 'www.wayne.com',OS = 'mac' WHERE ID IN(10,20,30)
15.5 MergeTree引擎
ClickHouse 是一款非常优秀的 OLAP 数据库,而 MergeTree 则是 ClickHouse 里最核心、最常用的存储引擎,本文主要解析 MergeTree 的数据存储逻辑,OLAP 数据库的数据通常是批量写入,MergeTree 引擎将每个批量写入存储成不同的文件,然后后台根据一定策略对文件进行合并,跟 LSM Tree 引擎的 Compaciton 逻辑比较类似
相信大家对将数据片段写入的方式不会太陌生,在kafka和ElasticSearch中使用的segment作为数据存储,以及HBase中以HFile作为数据存储,这些方式均采用了以下方式:
-
数据文件只能追加,不能对已有数据进行直接修改(即使是删除,也仅是添加一行记录表示删除标记)
-
为了防止文件数过多,会在后台按照一定规则进行合并
这种设计对于大量写入是非常有益的。
15.5.1 创建与存储
-
MergeTree在写入一批数据时,数据总会以数据片段的形式写入磁盘,且数据片段不可修改
-
为了避免片段过多,ClickHouse会通过后台线程,定期合并这些数据片段,属于相同分区的数据片段会被合并成一个新的片段
-
这种数据片段往复合并的特点,也是合并树名称的由来
1. 创建方式
-
语法
CREATE TABLE [IF NOT EXISTS] [db_name.]table_name ( name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr], name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr], 省略... ) ENGINE = MergeTree() [PARTITION BY expr] [ORDER BY expr] [PRIMARY KEY expr] [SAMPLE BY expr] [SETTINGS name=value, 省略...] CREATE TABLE IF NOT EXISTS yjxxt.mt ( mid String, mname String, createtime DateTime ) ENGINE = MergeTree() PARTITION BY toYYYYMM(createtime) ORDER BY (mid,mname) PRIMARY KEY mid; insert into yjxxt.mt values ('1','zs','2021-05-02'),('2','ls','2021-06-02'),('3','ww','2021-06-04') ; --可以通过system.tables 查看表的元数据信息
-
配置选项
-
PARTITION BY [选填]:分区键,用于指定表数据以何种标准进行分区。
-
分区键既可以是单个列字段,也可以通过元组的形式使用多个列字段,同时它也支持使用列表达式。
-
如果不声明分区键,则ClickHouse会生成一个名为all的分区。
-
合理使用数据分区,可以有效减少查询时数据文件的扫描范围。
-
-
ORDER BY [必填]:排序键,用于指定在一个数据片段内,数据以何种标准排序。
-
默认情况下主键(PRIMARY KEY)与排序键相同。
-
排序键既可以是单个列字段,例如ORDER BY CounterID,也可以通过元组的形式使用多个列字段,例如ORDERBY(CounterID,EventDate)。
-
当使用多个列字段排序时,以ORDERBY(CounterID,EventDate)为例,在单个数据片段内,数据首先会以CounterID排序,相同CounterID的数据再按EventDate排序。
-
-
PRIMARY KEY [选填]:主键,顾名思义,声明后会依照主键字段生成一级索引,用于加速表查询。
-
默认情况下,主键与排序键(ORDER BY)相同,所以通常直接使用ORDER BY代为指定主键,无须刻意通过PRIMARY KEY声明。
-
所以在一般情况下,在单个数据片段内,数据与一级索引以相同的规则升序排列。
-
与其他数据库不同,MergeTree主键允许存在重复数据(ReplacingMergeTree可以去重)。
-
-
SAMPLE BY [选填]:抽样表达式,用于声明数据以何种标准进行采样。
-
如果使用了此配置项,那么在主键的配置中也需要声明同样的表达式
ENGINE = MergeTree() ORDER BY (CounterID, EventDate, intHash32(UserID) SAMPLE BY intHash32(UserID)
-
抽样表达式需要配合SAMPLE子查询使用,这项功能对于选取抽样数据十分有用
-
-
SETTINGS:
-
index_granularity [选填]:
-
index_granularity对于MergeTree而言是一项非常重要的参数,它表示索引的粒度,默认值为8192。
-
MergeTree的索引在默认情况下,每间隔8192行数据才生成一条索引
-
-
index_granularity_bytes [选填]
-
在19.11版本之前,ClickHouse只支持固定大小的索引间隔,index_granularity控制,默认为8192。
-
在新版本中,它增加了自适应间隔大小的特性,即根据每一批次写入数据的体量大小,动态划分间隔大小。
-
而数据的体量大小,正是由index_granularity_bytes参数控制的,默认为10M(10×1024×1024),设置为0表示不启动自适应功能。
-
-
enable_mixed_granularity_parts [选填]
-
设置是否开启自适应索引间隔的功能,默认开启
-
-
merge_with_ttl_timeout [选填]
-
从19.6版本开始,MergeTree提供了数据TTL的功能
-
-
storage_policy [选填]:
-
从19.15版本开始,MergeTree提供了多路径的存储策略
-
-
-
2. 存储格式
-
MergeTree表引擎中的数据是拥有物理存储的,数据会按照分区目录的形式保存到磁盘之上
-
数据表的物理结构分层
-
一张数据表的完整物理结构分为3个层级,依次是数据表目录、分区目录及各分区下具体的数据文件
-
partition:
-
分区目录,余下各类数据文件(primary.idx、[Column].mrk、[Column].bin等)都是以分区目录的形式被组织存放的,属于相同分区的数据,最终会被合并到同一个分区目录,而不同分区的数据,永远不会被合并在一起
-
-
[Column].bin:
-
数据文件,使用压缩格式存储,默认为LZ4压缩格式,用于存储某一列的数据。由于MergeTree采用列式存储,所以每一个列字段都拥有独立的.bin数据文件,并以列字段名称命名(例如CounterID.bin、EventDate.bin等)
-
-
15.5.2 数据分区
通过之前的介绍我们已经知道在 MergeTree 数据表中,数据是以分区目录的形式进行组织的,每个分区的数据独立分开存储。借助这种形式,MergeTree 在查询数据时,可以跳过无用的数据文件,只在最小分区目录子集中查询。这里再强调一次,在 ClickHouse 中存在数据分区(partition)和数据分片(shard),但它们是完全不同的概念。数据分区是针对本地数据而言的,相当于是对数据的一种纵向切分,就类似将关系型数据中的一张大高表切成多张个头没那么高的子表,而数据分片则与 ClickHouse 集群相关,我们后面会说,我们目前都是单机的,所以不涉及数据分片
1. 数据分区规则
-
理解
-
MergeTree的数据分区的规则由分区ID决定的,而具体到每个数据分区所对应的ID,则是由分区键的取值决定的
-
分区键支持使用任何一个或一组字段表达式声明,其业务语义可以是年、月、日或者组织单位等任何一种规则
-
-
针对取值类型的不同,分区ID的生成逻辑目前有四种规则
-
不指定分区键
-
如果不使用分区键,即不使用PARTITION BY声明任何分区表达式,则分区ID默认取名为all,所有的数据都会被写入这个all分区
-
-
使用整形
-
如果分区键取值属于整型(兼容UInt64,包括有符号整型和无符号整型),且无法转换为日期类型YYYYMMDD格式
-
则直接按照该整型的字符形式输出,作为分区ID的取值
-
-
使用日期类型
-
如果分区键取值属于日期类型,或者是能够转换为YYYYMMDD格式的整型
-
则使用按照YYYYMMDD进行格式化后的字符形式输出,并作为分区ID的取值
-
-
使用其他类型
-
如果分区键取值既不属于整型,也不属于日期类型
-
例如String、Float等,则通过128位Hash算法取其Hash值作为分区ID的取值
-
-
2. 分区目录命名
3. 分区目录合并