ClickHouse 是俄罗斯的 Yandex 于2016年开源的列式存储数据库管理系统(DBMS),主要用于在线分析处理查询(OLAP),能够使用SQL查询,实时生成分析数据报告。
真正的面向列的DBMS
数据压缩
磁盘存储数据
多核并行处理
在多个服务器上分布式处理
支持SQL
向量化引擎
实时数据更新
支持近似计算
支持数据复制和对数据完整性
没有完整的事务支持,不支持Transaction想快就别Transaction
缺少完整Update/Delete操作,缺少高频率、低延迟的修改或删除已存在数据的能力,仅用于批量删除或修改数据。
聚合结果必须小于一台机器的内存大小
支持有限操作系统,正在慢慢完善
不适合Key-value存储,不支持Blob等文档型数据库
ClickHouse 是一个真正的列式数据库管理系统(DBMS)。在 ClickHouse 中,数据始终是按列存储的,包括矢量(向量或列块)执行的过程
**
field:
**列中某一个值
column:
—列数据
DataType:
描述了列的数据类型
Block:
属于column datatype列名的组合体,表操作的对象是Block
BlockStream
:负责块数据的读取和写出
Format:
向客户端展示数据的方式
数据读写IO:
有一个缓冲区负责数据的读写
数据表table:
多个列的一个集合体读取数据的时候以表为单位进行操作操作的时候以BlockStream操作Block
解析器Parser:
对Sql语句进行解析
解释器Interpreter:
解析SQL语句
函数Functions:
提供两类函数—普通函数(Functions)和聚合函数(Aggregate Functions)
普通函数Functions:
普通函数不会改变行数 - 它们的执行看起来就像是独立地处理每一行数据
聚合函数Aggregate Functions:
相比无状态的普通函数,聚合函数是状态函数。它们将传入的值激活到某个状态,并允许你从该状态获取结果。聚合状态可以被序列化和反序列化,可以在分布式查询执行期间通过网络传递或者在内存不够的时候将其写到硬盘。
Cluster与Replication:
ClickHouse的集群由分片 ( Shard ) 组成,而每个分片又通过副本 ( Replica ) 组成。
Column和Field是ClickHouse数据最基础的映射单元.
内存中的一列数据由一个Column对象表示.
IDataType 负责序列化和反序列化:读写二进制或文本形式的列或单个值构成的块。 IDataType
直接与表的数据类型相对应。
IDataType 与 IColumn 之间的关联并不大。不同的数据类型在内存中能够用相同的 IColumn 实
现来表示。
IDataType 仅存储元数据。
数据的序列化和反序列化工作由DataType负责。IDataType接口定义了许多正反序列化的方法,它
们成对出现。IDataType也使用了泛化的设计模式,具体方法的实现逻辑由对应数据类型的实例承
载。
DataType虽然负责序列化相关工作,但它并不直接负责数据的读取,而是转由从Column或Field对
象获取。
Block 是表示内存中表的子集(chunk)的容器,是由三元组: (IColumn, IDataType, 列名)
构成的集合。
ClickHouse内部的数据操作是面向Block对象进行的,并且采用了流的形式。Block对象可以看作数
据表的子集。
Block并没有直接聚合Column和DataType对象,而是通过ColumnWithTypeAndName对象进行间
接引用。
块流用于处理数据。我们可以使用块流从某个地方读取数据,执行数据转换,或将数据写到某个地
方。
Block流操作有两组顶层接口:
IBlockInputStream负责数据的读取和关系运算, IBlockInputStream 具有 read 方法,其
能够在数据可用时获取下一个块。
IBlockOutputStream负责将数据输出到下一环节。 IBlockOutputStream 具有 write 方
法,其能够将块写到某处。
IBlockInputStream接口总共有60多个实现类,这些实现类大致可以分为三类:
第一类用于处理数据定义的DDL操作
第二类用于处理关系运算的相关操作
第三类则是与表引擎呼应,每一种表引擎都拥有与之对应的BlockInputStream实现
IBlockOutputStream的设计与IBlockInputStream如出一辙。这些实现类基本用于表引擎的相关处
理,负责将数据写入下一环节或者最终目的地。
对于面向字节的输入输出,有 ReadBuffer 和 WriteBuffer 这两个抽象类。
ReadBuffer 和 WriteBuffer 由一个连续的缓冲区和指向缓冲区中某个位置的一个指针组成。
ReadBuffer 和 WriteBuffer 的实现用于处理文件、文件描述符和网络套接字(socket),也用
于实现压缩和其它用途。
在数据表的底层设计中并没有所谓的Table对象
表由 IStorage 接口表示。该接口的不同实现对应不同的表引擎。
表引擎是ClickHouse的一个显著特性,不同的表引擎由不同的子类实现。
IStorage 中最重要的方法是 read 和 write ,除此之外还有 alter 、 rename 和 drop 等方法。
表的 read 方法能够返回多个 IBlockInputStream 对象以允许并行处理数据。多个块输入流能
够从一个表中并行读取。
AST 查询被传递给 read 方法,表引擎可以使用它来判断是否能够使用索引,从而从表中读取更少的数据。
在数据表的底层设计中并没有所谓的Table对象
表由 IStorage 接口表示。该接口的不同实现对应不同的表引擎。
表引擎是ClickHouse的一个显著特性,不同的表引擎由不同的子类实现。
IStorage 中最重要的方法是 read 和 write ,除此之外还有 alter 、 rename 和 drop 等方
法。
表的 read 方法能够返回多个 IBlockInputStream 对象以允许并行处理数据。多个块输入流能
够从一个表中并行读取。
AST 查询被传递给 read 方法,表引擎可以使用它来判断是否能够使用索引,从而从表中读取更少
的数据
查询由一个手写递归下降解析器解析。比如, ParserSelectQuery 只是针对查询的不同部分递归
地调用下层解析器。
解析器创建 AST 。 AST 由节点表示,节点是 IAST 的实例。
解释器负责从 AST 创建查询执行流水线。
简单的解释器,如 InterpreterExistsQuery 和 InterpreterDropQuery
复杂的解释器,如 InterpreterSelectQuery 。
查询执行流水线由块输入或输出流组成。
SELECT 查询的解释结果是从 FROM 字句的结果集中读取数据的 IBlockInputStream ;
INSERT 查询的结果是写入需要插入的数据的 IBlockOutputStream ;
Parser分析器可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。不同的SQL语句,
会经由不同的Parser实现类解析。
普通函数不会改变行数 - 它们的执行看起来就像是独立地处理每一行数据。
实际上,函数不会作用于一个单独的行上,而是作用在以 Block 为单位的数据上,以实现向量查
询执行。
普通函数由IFunction接口定义,拥有数十种函数实现,采用向量化的方式直接作用于一整列数
据。
聚合函数是状态函数。它们将传入的值激活到某个状态,并允许你从该状态获取结果。
聚合函数由IAggregateFunction接口定义,相比无状态的普通函数,聚合函数是有状态的。
以COUNT聚合函数为例,其AggregateFunctionCount的状态使用整型UInt64记录。
聚合状态可以被序列化和反序列化,以在分布式查询执行期间通过网络传递或者在内存不够的时候
将其写到硬盘。
ClickHouse的集群由分片 ( Shard ) 组成,而每个分片又通过副本 ( Replica ) 组成。
这种分层的概念,在一些流行的分布式系统中十分普遍。
ClickHouse的1个节点只能拥有1个分片,也就是说如果要实现1分片、1副本,则至少需要部
署2个服务节点。
分片只是一个逻辑概念,其物理承载还是由副本承担的
[root@node01 ~]# systemctl start clickhouse-server.service
[root@node01 ~]# clickhouse-client -h 192.168.88.101 -u default --password
默认用户 default 密码 123456
查看节点的信息
select * from system.clusters;
固定长度的整型,包括有符号整型或无符号整型。
整型范围(-2n-1~2n-1-1):
Int8 - [-128 : 127]
Int16 - [-32768 : 32767]
Int32 - [-2147483648 : 2147483647]
Int64 - [-9223372036854775808 : 9223372036854775807]
无符号整型范围(0~2n-1):
无符号数据表示数量,只有正值
UInt8 - [0 : 255]
UInt16 - [0 : 65535]
UInt32 - [0 : 4294967295]
UInt64 - [0 : 18446744073709551615]
使用场景: 个数、数量、也可以存储型 id。
Float32 - float
Float64 – double
建议尽可能以整数形式存储数据。例如,将固定精度的数字转换为整数值,如时间用毫秒为单位表示,因为浮点型进行计算时可能引起四舍五入的误差使用场景:一般数据值比较小,不涉及大量的统计计算,精度要求不高的时候。比如
保存商品的重量。
没有单独的类型来存储布尔值。可以使用 UInt8 类型,取值限制为 0 或 1
有符号的浮点数,可在加、减和乘法运算过程中保持精度。对于除法,最低有效数字会被丢弃(不舍入)。
有三种声明: ➢ Decimal32(s),相当于 Decimal(9-s,s),有效位数为 1~9 ➢ Decimal64(s),相当于 Decimal(18-s,s),有效位数为 1~18 ➢ Decimal128(s),相当于 Decimal(38-s,s),有效位数为 1~38
- 1
- 2
- 3
- 4
s 标识小数位
使用场景: 一般金额字段、汇率、利率等字段为了保证小数点精度,都使用 Decimal进行存储。
string
字符串可以任意长度的。它可以包含任意的字节集,包含空字节。
)FixedString(N)
相当于Char固定长度 N 的字符串,N 必须是严格的正自然数。当服务端读取长度小于 N 的字符
串时候,通过在字符串末尾添加空字节来达到 N 字节长度。 当服务端读取长度大于 N 的
字符串时候,将返回错误消息。
与 String 相比,极少会使用 FixedString,因为使用起来不是很方便
**使用场景:**名称、文字描述、字符型编码。 固定长度的可以保存一些定长的内容,比如一些编码,性别等但是考虑到一定的变化风险,带来收益不够明显,所以定长字符串使用
意义有限。
目前 ClickHouse 有三种时间类型
➢ Date 接受年-月-日的字符串比如 ‘2019-12-16’
➢ Datetime 接受年-月-日 时:分:秒的字符串比如 ‘2019-12-16 20:50:10’
➢ Datetime64 接受年-月-日 时:分:秒.亚秒的字符串比如‘2019-12-16 20:50:10.66’
日期类型,用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值。
包括 Enum8 和 Enum16 类型。Enum 保存 ‘string’= integer 的对应关系。
Enum8 用 ‘String’= Int8 对描述。
Enum16 用 ‘String’= Int16 对描述。
使用场景:对一些状态、类型的字段算是一种空间优化,也算是一种数据约束。但是实际使用中往往因为一些数据内容的变化增加一定的维护成本,甚至是数据丢失问题。所以谨慎使用
Array(T)::由 T 类型元素组成的数组 创建数据:array(T)或[],类型必须相同
T 可以是任意类型,包含数组类型。 但不推荐使用多维数组,ClickHouse 对多维数组
的支持有限。例如,不能在 MergeTree 表中存储多维数组。
(1)创建数组方式 1,使用 array 函数array(T) hadoop102 :) SELECT array(1, 2) AS x, toTypeName(x) ;
- 1
- 2
数据库起到了
命名空间的作用,可以有效规避命名冲突的问题
,也为后续的数据隔离提供了支撑。任何一张数据表,都必须归属在某个数据库之下。
数据库引擎:
Ordinary:默认引擎
- 在绝大多数情况下我们都会使用默认引擎,使用时无须刻意声明。在此数据库下可以使
用任意类型的表引擎。Dictionary:字典引擎
- 此类数据库会自动为所有数据字典创建它们的数据表
Memory:内存引擎
- 用于存放临时数据。此类数据库下的数据表只会停留在内存中,不会涉及任何磁盘操
作,当服务重启后数据会被清除。Lazy:日志引擎
- 此类数据库下只能使用log系列的表引擎。
MySQL:MySQL引擎
- 此类数据库下会自动拉取远端MySQL中的数据,并为它们创建MySQL表引擎的数据表,
ClickHouse目前提供了三种最基本的建表方法:
第一种是常规定义方法
使用[db_name.]参数可以为数据表指定数据库,如果不指定此参数,则默认会使用default数据库
第二种定义方法是复制其他表的结构
支持在不同的数据库之间复制表结构
第三种定义方法是通过SELECT子句的形式创建
根据SELECT子句建立相应的表结构,同时还会将SELECT子句查询的数据顺带写入
ClickHouse和大多数数据库一样,使用
DESC查询可以返回数据表的定义结构
。
如果想删除一张数据表,则可以使用下面的DROP
语句:DROP TABLE [IF EXISTS] [db_name.]table_name
- 1
ClickHouse也有临时表的概念,创建临时表的方法是在普通表的基础之上添加
TEMPORARY
关键字特点:
它的生命周期是会话绑定的,所以它只支持Memory表引擎,如果会话结束,数据表就会被销毁;
临时表不属于任何数据库,所以在它的建表语句中,既没有数据库参数也没有表引擎参数。
临时表的优先级是大于普通表。当两张数据表名称相同的时候,会优先读取临时表的数据
分区
(partition)和数据分片
(shard)是完全不同的两个概念。
追加新字段
ALTER TABLE xxx add column item String DEFAULT 'mac' 默认最后一行加
ALTER TABLE xxx add column app String AFTER ID 在ID列后面加新字段
修改数据类型
ALTER TABLE content MODIFY COLUMN ip IPv4
修改备注
ALTER TABLE content comment column ID '主键列'
删除已有字段
ALTER TABLE content DROP COLUMN [IF EXISTS] name
移动数据表
RENAME TABLE default.content TO test.test
清空数据表
drop table content 物理删除,立即释放空间;删除表数据+表空间
truncate table content 物理删除,立即释放空间;删除表数据,保留表结构
delete table content [FROM] 逻辑删除,删除的数据做标记;逐行删除表数据,保留表空间
查看表定义结构:
show create table content 原样输出
desc test 表格显示
删除表:
drop table content 物理删除,立即释放空间;删除表数据+表空间
truncate table content 物理删除,立即释放空间;删除表数据,保留表结构
delete table content [FROM] 逻辑删除,删除的数据做标记;逐行删除表数据,保留表空间
ClickHouse拥有两种视图,
普通视图
和物化视图
,其中物化视图拥有独立的存储,而普通视图只是一层简单的查询代理。
普通视图
物化视图
POPULATE
修饰符决定了物化视图的初始化策略:
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()
表引擎是 ClickHouse 的一大特色。可以说, 表引擎决定了如何存储表的数据。包括:
➢ 数据的存储方式和位置,写到哪里以及从哪里读取数据。
➢ 支持哪些查询以及如何支持。
➢ 并发数据访问。
➢ 索引的使用(如果存在)。
➢ 是否可以执行多线程请求。
➢ 数据复制参数。
表引擎的使用方式就是必须显式在创建表时定义该表使用的引擎,以及引擎使用的相关参数。
特别注意:引擎的名称大小写敏感
TinyLog
以列文件的形式保存在磁盘上,不支持索引,没有并发控制。一般保存少量数据的小表,
生产环境上作用有限。可以用于平时练习测试用。如:
create table t_tinylog ( id String, name String) engine=TinyLog;
Memory
- 内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的性能表现(超过 10G/s)。
- 一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大(上限大概 1 亿行)的场景。
MergeTree
- ClickHouse 中最强大的表引擎当属 MergeTree(
合并树
)引擎及该系列(MergeTree)中的其他引擎,支持索引和分区,地位可以相当于 innodb 之于 Mysql。而且基于 MergeTree,还衍生出了很多小弟,也是非常有特色的引擎。
合并树
名称的由来。1)建表语句
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);
2)插入数据
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');
MergeTree 其实还有很多参数(绝大多数用默认值即可),但是三个参数是更加重要的,也涉及了关于 MergeTree 的很多概念。
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可以去
重)。
3个层级
,依次是数据表目录、分区目录及各分区下具体的数据文件MergeTree数据分区的规则由分区ID
决定,而具体到每个数据分区所对应的ID,则是由分区键的取值决定的。
分区键支持使用任何一个或一组字段表达式声明,其业务语义可以是年、月、日或者组织单位等任何一种规则。
针对取值数据类型的不同,分区ID的生成逻辑目前拥有四种规则:
不指定分区键:
如果不使用分区键,即不使用PARTITION BY声明任何分区表达式,则
分区ID默认取名为all
,所有的数据都会被写入这个all分区。
使用整型:
如果分区键取值属于整型(兼容UInt64,包括有符号整型和无符号整型),且无法转换为日期类型YYYYMMDD格式则直接按照该整型的字符形式输出,作为分区ID的取值。
使用日期类型:
如果分区键取值属于日期类型,或者是能够转换为YYYYMMDD格式的整型
则使用按照YYYYMMDD进行格式化后的字符形式输出,并作为分区ID的取值。
使用其他类型:
如果分区键取值既不属于整型,也不属于日期类型
例如String、Float等,则通过128位Hash算法取其Hash值作为分区ID的取值。
201905表示分区目录的ID;
1_1分别表示最小的数据块编号与最大的数据块编号;
而最后的_0则表示目前合并的层级。
PartitionID_MinBlockNum_MaxBlockNum_Level
PartitionID:
分区ID
MinBlockNum和MaxBlockNum:顾名思义,最小数据块编号
与最大数据块编号
。
Level:合并的层级
,可以理解为某个分区被合并过的次数,或者这个分区的年龄。数值越高
表示年龄越大。
MergeTree的分区目录和传统意义上其他数据库有所不同。
首先,MergeTree的分区目录并不是在数据表被创建之后就存在的,而是在数据写入过程中被创建的。
也就是说
如果一张数据表没有任何数据,那么也不会有任何分区目录存在
。
其次,它的分区目录在建立之后也并不是一成不变的。
新目录名称的合并方式遵循规则:
MinBlockNum:
取同一分区内所有目录中最小的MinBlockNum值。
MaxBlockNum:
取同一分区内所有目录中最大的MaxBlockNum值。
Level:
取同一分区内最大Level值并加1。
PRIMARY KEY
定义,待主键定义之后,MergeTree会依据index_granularity间隔(默认8192行),为数据表生成一级索引并保存至primary.idx文件内,索引数据按照PRIMARYKEY排序。primary.idx文件内的一级索引采用稀疏索引实现。
稠密索引
中每一行索引标记都会对应到一行具体的数据记录。
稀疏索引
中每一行索引标记对应的是一段数据,而不是一行。
稀疏索引的优势是显而易见的,它仅需使用少量的索引标记就能够记录大量数据的区间位置信息,且数据量越大优势越为明显。
以默认的索引粒度(8192)为例,MergeTree只需要12208行索引标记就能为1亿行数据记录提供索引。
由于稀疏索引占用空间小,所以primary.idx内的索引数据常驻内存,取用速度自然极快。
索引文件查看命令
od -An -i -w4 primary.idx
由于是稀疏索引,所以MergeTree需要间隔index_granularity行数据才会生成一条索引记录,其索
引值会依据声明的主键字段获取。
单主键
第0(81920)行CounterID取值57,第8192(81921)行CounterID取值1635,而第16384(8192*2)行CounterID取值3266
最终索引数据将会是5716353266。
MarkRange
MarkRange在ClickHouse中是用于定义标记区间的对象。
MergeTree按照index_granularity的间隔粒度,将一段完整的数据划分成了多个小的间隔数据段,一个具体的数据段即是一个MarkRange。
MarkRange与索引编号对应,使用start和end两个属性表示其区间范围。
通过与start及end对应的索引编号的取值,即能够得到它所对应的数值区间。而数值区间表示了此MarkRange包含的数据范围。
案例分析
主键ID为String类型,ID的取值从A000开始,后面依次为A001、A002……直至A189为止。
MergeTree的索引粒度index_granularity=3
MergeTree会将此数据片段划分成189/3=63个小的MarkRange
索引查询其实就是两个数值区间的交集判断。
一个区间是由基于主键的查询条件转换而来的条件区间;
一个区间是刚才所讲述的与MarkRange对应的数值区间。
查询步骤
- 生成查询条件区间:首先,将查询条件转换为条件区间。
- 递归交集判断:以递归的形式,依次对MarkRange的数值区间与条件区间做交集判断。从最大的区间[A000,+inf)开始:
- 如果不存在交集,则直接通过剪枝算法优化此整段MarkRange。
- 如果存在交集,且MarkRange步长大于8(end-start),则将此区间进一步拆分成8个子区间,并重复此规则,继续做递归交集判断。
- 如果存在交集,且MarkRange不可再分解(步长小于8),则记录MarkRange并返回。
- 合并MarkRange区间:将最终匹配的MarkRange聚在一起,合并它们的范围。
二级索引又称跳数索引
,由数据的聚合信息构建而成。
根据索引类型的不同,其聚合信息的内容也不同。跳数索引的目的与一级索引一样,也是帮助查询时减少数据扫描的范围。
跳数索引在默认情况下是关闭的,需要设置allow_experimental_data_skipping_indices
SET allow_experimental_data_skipping_indices = 1
跳数索引需要在CREATE语句内定义,它支持使用元组和表达式的形式声明,其完整的定义语法
INDEX index_name expr TYPE index_type(…) GRANULARITY granularity
MergeTree共支持4种跳数索引,分别是minmax、set、ngrambf_v1和tokenbf_v1。一张数据表支持同时声明多个跳数索引。
minmax:
minmax索引记录了
一段数据内的最小和最大极值
,其索引的作用类似分区目录的minmax索引,能够快速跳过无用的数据区间
set:
set索引直接记录了声明字段或表达式的取值(
唯一值,无重复
),其完整形式为set(max_rows),其中max_rows是一个阈值,表示在一个index_granularity内,索引最多记录的数据行数。
ngrambf_v1:
- ngrambf_v1索引记录的是数据短语的
布隆表过滤器
,只支持String和FixedString数据类型。- ngrambf_v1只能够提升in、notIn、like、equals和notEquals查询的性能
- 其完整形式为
ngrambf_v1(n,size_of_bloom_filter_in_bytes,number_of_hash_functions,random_seed)。
- 这些参数是一个布隆过滤器的标准输入,如果你接触过布隆过滤器,应该会对此十分熟悉。它们具体的含义如下:
- n:token长度,依据n的长度将数据切割为token短语。
- size_of_bloom_filter_in_bytes:布隆过滤器的大小。
- number_of_hash_functions:布隆过滤器中使用Hash函数的个数。
- random_seed:Hash函数的随机种子。
tokenbf_v1:
tokenbf_v1索引是ngrambf_v1的变种,同样也是一种
布隆过滤器索引
。
tokenbf_v1除了短语token的处理方法外,其他与ngrambf_v1是完全一样的。
tokenbf_v1会自动按照非字符的、数字的字符串分割token
MergeTree
中,数据按列存储。具体到每个列字段,每个列字段都拥有一个与之对应的.bin
数据文件(物理存储)。.bin
文件只会保存当前分区片段内的这一部分数据。**存储方式
**大致流程是:首先数据压缩 -> 按照ORDER BY的声明排序 -> 数据以多个压缩数据块的形式被组织写入.bin文件头信息
和压缩数据
两部分组成。头信息固定使用9位字节表示,具体由1个UInt8(1字节)整型和2个UInt32(4字节)整型组成,分别代表使用的压缩算法类型**、压缩后的数据大小和压缩前的数据大小。size<64KB
:**如果单个批次数据小于64KB,则继续获取下一批数据,直至累积到size>=64KB时,生成下一个压缩数据块。64KB<=size<=1MB
:**如果单个批次数据大小恰好在64KB与1MB之间,则直接生成下一个压缩数据块。size>1MB
:**如果单个批次数据直接超过1MB,则首先按照1MB大小截断并生成数据标记作为
衔接一级索引和数据的桥梁
,其像极了做过标记小抄的书签,而且书本中每个一级章节都拥有各自的书签。
数据标记文件和索引区间是对齐的。都是按照index_ granularity
的粒度间隔划分。
数据标记文件和.bin文件也是一一对应。每一个列字段[column].bin文件都有一个对应的[column].mrk
数据标记文件,用于记录数据在.bin
文件中偏移量信息。
一行标记数据使用元组
表示,包含两个整型数据的偏移信息(压缩数据块的起始偏移量
,压缩块解压后未压缩数据的起始偏移量
)。
每一行标记数据都表示了一个片段的数据(默认8192行)在.bin压缩文件中的读取位置信息。标记数据与一级索引不同,它不能常驻内存,而是使用LRU(最近最少使用)缓存策略加快其取用速度。
标记查看命令
od -An -l name.mrk
**MergeTree在读取数据时,必须通过标记数据的位置信息才能够找到所需要的数据。**整个查找过程
大致可以分为读取压缩数据块
和读取数据
两个步骤
index_granularity
的粒度加载特定一小段 primaryindex_granularity
索引粒度,会分别生成primary.idx
一级索引、每一个列字段的.mrk
数据标记和.bin
压缩数据文件。该引擎没有特殊功能,适合保存历史明细.
- 19.15版本之前,MergeTree只支持单路径存储,所有的数据都会被写入config.xml配置中path指
定的路径下,即使服务器挂载了多块磁盘,也无法有效利用这些存储空间。- 19.15版本开始,MergeTree实现了自定义存储策略的功能,支持以数据分区为最小移动单元,将
分区目录写入多块磁盘目录。
该引擎适合于经常要根据’主键’进行数据更新的数据(upsert),主键加引号是因为,其实是根据
order by
定义的字段而不是根据primary key
的字段去重的.
AggregatingMergeTree更为常见的应用方式是结合物化视图使用,将它作为物化视图的表引擎。
- CollapsingMergeTree就是一种通过
以增代删
的思路,支持行级数据修改和删除的表引擎。它通过
定义一个sign标记位字段,记录数据行的状态。- 如果sign标记为1,则表示这是一行有效的数据;如果sign标记为-1,则表示这行数据需要被删除。
- 当CollapsingMergeTree分区合并时,同一数据分区内,sign标记为1和-1的一组数据会被抵消删除。
- 这种1和-1相互抵消的操作,犹如将一张瓦楞纸
折叠
了一般。
此引擎是CollapingMergeTree的升级版,除了一个Int8类型的flag字段还需要一个Int8型的版本字段:
应用场景,统计在线玩家
:比如游戏登陆时候要把正在登陆的玩家信息放到表中,代表已经登陆,但是下线的时候再插入一条数据,使其折叠,此时合并文件后(或者手动调用optimize语句)数据消失,代表玩家已经下线.
MySQL表引擎可以与MySQL数据库中的数据表建立映射,并通过SQL向其发起远程查询,包括SELECT和INSERT
ENGINE = MySQL(‘host:port’, ‘database’, ‘table’, ‘user’, ‘password’[,replace_query, ‘on_duplicate_clause’])
host:port表示MySQL的地址和端口。
database表示数据库的名称。
table表示需要映射的表名称。
user表示MySQL的用户名。
password表示MySQL的密码。
replace_query默认为0,对应MySQL的REPLACE INTO语法。如果将它设置为1,则会用
REPLACEINTO代替INSERT INTO。
on_duplicate_clause默认为0,对应MySQL的ON DUPLICATE KEY语法。如果需要使用该设置,则必须将replace_query设置成0。目前MySQL表引擎不支持任何UPDATE和DELETE操作
首先是Kafka数据表A,它充当的角色是一条数据管道,负责拉取Kafka中的数据。
接着是另外一张任意引擎的数据表B,它充当的角色是面向终端用户的查询表,在生产环境中通常是MergeTree系列。
最后,是一张物化视图C,它负责将表A的数据实时同步到表B。
将数据全量放在内存中,对于表引擎来说是一把双刃剑:
- 一方面,这意味着拥有较好的查询性能;
- 另一方面,如果表内装载的数据量过大,可能会带来极大的内存消耗和负担。
StripeLog表引擎的存储结构由固定的3个文件组成,它们分别是:
data.bin:数据文件,所有的列字段使用同一个文件保存,它们的数据都会被写入data.bin。
index.mrk:数据标记,保存了数据在data.bin文件中的位置信息。利用数据标记能够使用多个线程,以并行的方式读取data.bin内的压缩数据块,从而提升数据查询的性能。
sizes.json:元数据文件,记录了data.bin和index.mrk大小的信息。
Log表引擎结合了TinyLog表引擎和StripeLog表引擎的长处,是日志家族系列中性能最高的表引
擎。
Log表引擎的存储结构由3个部分组成:
[column].bin:数据文件,数据文件按列独立存储,每一个列字段都拥有一个与之对应的.bin文件。
marks.mrk:数据标记,统一保存了数据在各个[column].bin文件中的位置信息。利用数据标记能够使用多个线程,以并行的方式读取.bin内的压缩数据块,从而提升数据查询的性能。
sizes.json:元数据文件,记录了[column].bin和__marks.mrk大小的信息。
在数据仓库的设计中,数据按年分表存储,例如test_table_2018、test_table_2019和test_table_2020。假如现在需要跨年度查询这些数据 ?
Merge表引擎就如同一层使用了门面模式的代理,它本身不存储任何数据,也不支持数据写入。
被代理查询的数据表被要求处于同一个数据库内,且拥有相同的表结构,但是它们可以使用不同的
表引擎以及不同的分区定义
ENGINE = Merge(database, table_name)
database表示数据库名称;
table_name表示数据表的名称,它支持使用正则表达式
在日常运转的过程中,数据查询也是ClickHouse的主要工作之一。ClickHouse完全使用SQL作为查
询语言,能够以SELECT查询语句的形式从数据库中选取数据,这也是它具备流行潜质的重要原
因。虽然ClickHouse拥有优秀的查询性能,但是我们也不能滥用查询,掌握ClickHouse所支持的各
种查询子句,并选择合理的查询形式是很有必要的。使用不恰当的SQL语句进行查询不仅会带来低
性能,还可能导致不可预知的系统错误。
ClickHouse对于SQL语句的解析是大小写敏感的,这意味着SELECT a和SELECT A表示的语义是不
相同的。
ClickHouse目前支持的查询子句如下所示:
[WITH expr |(subquery)]
SELECT [DISTINCT] expr
[FROM [db.]table | (subquery) | table_function] [FINAL]
[SAMPLE expr]
[[LEFT] ARRAY JOIN]
[GLOBAL] [ALL|ANY|ASOF] [INNER | CROSS | [LEFT|RIGHT|FULL [OUTER]] ]
JOIN (subquery)|table ON|USING columns_list
[PREWHERE expr]
[WHERE expr]
[GROUP BY expr] [WITH ROLLUP|CUBE|TOTALS]
[HAVING expr]
[ORDER BY expr]
[LIMIT [n[,m]]
[UNION ALL]
[INTO OUTFILE filename]
[FORMAT format]
[LIMIT [offset] n BY columns]
个查询的结果集。
Merge表引擎可以代理查询任意数量的数据表,这些查询会异步且并行执行,并最终合成一个
结果集返回
被代理查询的数据表被要求处于同一个数据库内,且拥有相同的表结构,但是它们可以使用不同的
表引擎以及不同的分区定义
ENGINE = Merge(database, table_name)
database表示数据库名称;
table_name表示数据表的名称,它支持使用正则表达式
在日常运转的过程中,数据查询也是ClickHouse的主要工作之一。ClickHouse完全使用SQL作为查
询语言,能够以SELECT查询语句的形式从数据库中选取数据,这也是它具备流行潜质的重要原
因。虽然ClickHouse拥有优秀的查询性能,但是我们也不能滥用查询,掌握ClickHouse所支持的各
种查询子句,并选择合理的查询形式是很有必要的。使用不恰当的SQL语句进行查询不仅会带来低
性能,还可能导致不可预知的系统错误。
ClickHouse对于SQL语句的解析是大小写敏感的,这意味着SELECT a和SELECT A表示的语义是不
相同的。
ClickHouse目前支持的查询子句如下所示:
[WITH expr |(subquery)]
SELECT [DISTINCT] expr
[FROM [db.]table | (subquery) | table_function] [FINAL]
[SAMPLE expr]
[[LEFT] ARRAY JOIN]
[GLOBAL] [ALL|ANY|ASOF] [INNER | CROSS | [LEFT|RIGHT|FULL [OUTER]] ]
JOIN (subquery)|table ON|USING columns_list
[PREWHERE expr]
[WHERE expr]
[GROUP BY expr] [WITH ROLLUP|CUBE|TOTALS]
[HAVING expr]
[ORDER BY expr]
[LIMIT [n[,m]]
[UNION ALL]
[INTO OUTFILE filename]
[FORMAT format]
[LIMIT [offset] n BY columns]