ClickHouse概述
1.1 ClickHouse概述
- ClickHouse 是俄罗斯的 Yandex 于 2016 年开源的列式存储数据库(DBMS),使用 C++语言编
写,主要用于在线分析处理查询(OLAP),能够使用 SQL 查询实时生成分析数据报告。
1.2ClickHouse概述
- 真正的列式数据库管理系统
- ClickHouse不单单是一个数据库, 它是一个数据库管理系统。因为它允许在运行时创建表和数
据库、加载数据和运行查询,而无需重新配置或重启服务
- 数据压缩
- 除了在磁盘空间和CPU消耗之间进行不同权衡的高效通用压缩编解码器之外,ClickHouse还提
供针对特定类型数据的专用编解码器,这使得ClickHouse能够与更小的数据库(如时间序列数
据库)竞争并超越它们。
- 数据的磁盘存储
- ClickHouse被设计用于工作在传统磁盘上的系统,它提供每GB更低的存储成本,但如果可以
使用SSD和内存,它也会合理的利用这些资源
- 多核心并行处理
- ClickHouse会使用服务器上一切可用的资源,从而以最自然的方式并行处理大型查询。
- 多服务器分布式处理
- 在ClickHouse中,数据可以保存在不同的shard上,每一个shard都由一组用于容错的replica组
成,查询可以并行地在所有shard上进行处理。这些对用户来说是透明的
- 支持SQL
- ClickHouse支持一种基于SQL的声明式查询语言,它在许多情况下与ANSI SQL标准相同。
- 支持的查询GROUP BY, ORDER BY, FROM, JOIN, IN以及非相关子查询。
- 相关(依赖性)子查询和窗口函数暂不受支持,但将来会被实现。
- 向量引擎(部分)
- 为了高效的使用CPU,数据不仅仅按列存储,同时还按向量(列的一部分)进行处理,这样可以
更加高效地使用CPU。 - 向量化执行是寄存器硬件层面上的特性。可以理解为消除程序中循环的优化。为了实现向量化
执行,需要利用CPU的SIMD指令(Single Instruction Multiple Data),即用单条指令处理多条
数据。现代计算机系统概念中,他是利用数据并行来提高性能的一种实现方式,,他的原理是
在CPU寄存器层面实现数据并行的实现原理。
- 实时的数据更新
- ClickHouse支持在表中定义主键。为了使查询能够快速在主键中进行范围查找,数据总是以增量的方式有序的存储在MergeTree中。因此,数据可以持续不断地高效的写入到表中,并且写
入的过程中不会存在任何加锁的行为。
- 索引
- 按照主键对数据进行排序,这将帮助ClickHouse在几十毫秒以内完成对数据特定值或范围的查找。
- 适合在线查询
- 在线查询意味着在没有对数据做任何预处理的情况下以极低的延迟处理查询并将结果加载到用
户的页面中。
- 支持近似计算
- ClickHouse提供各种各样在允许牺牲数据精度的情况下对查询进行加速的方法
- 用于近似计算的各类聚合函数,如:distinct values, medians, quantiles
- 基于数据的部分样本进行近似查询。这时,仅会从磁盘检索少部分比例的数据。
- 不使用全部的聚合条件,通过随机选择有限个数据聚合条件进行聚合。这在数据聚合条件满足某些分布条件下,在提供相当准确的聚合结果的同时降低了计算资源的使用。
- 支持数据复制和数据完整性
- ClickHouse使用异步的多主复制技术。当数据被写入任何一个可用副本后,系统会在后台将数
据分发给其他副本,以保证系统在不同副本上保持相同的数据。在大多数情况下ClickHouse能
在故障后自动恢复,在一些少数的复杂情况下需要手动恢复。
- 角色的访问控制
- ClickHouse使用SQL查询实现用户帐户管理,并允许角色的访问控制,类似于ANSI SQL标准
和流行的关系数据库管理系统。
- 缺陷
- 没有完整的事务支持。
- 缺少高频率,低延迟的修改或删除已存在数据的能力。仅能用于批量删除或修改数据,但这符合 GDPR。
- ClickHouse的Upadate和Delete是由Alter变种实现。
- 稀疏索引使Clickhouse不太有效地查询钥匙来检索单个行。
1.3 ClickHouse应用场景
官方案例:https://clickhouse.com/docs/en/about-us/adopters/
可以应用以下场景:
1.电信行业用于存储数据和统计数据使用。
2.新浪微博用于用户行为数据记录和分析工作。
3.用于广告网络和RTB,电子商务的用户行为分析。
4.信息安全里面的日志分析。
5.检测和遥感信息的挖掘。
6.商业智能。
7.网络游戏以及物联网的数据处理和价值数据分析。
ClickHouse数据类型
2.1 整型
包括有符号整型或无符号整型。
有符号整型范围(-2n-1~2n-1-1):
名称 | 范围 |
Int8 | [-128 : 127] |
Int16 | [-32768 : 32767] |
Int32 | [-2147483648 : 2147483647] |
Int64 | [-9223372036854775808 : 9223372036854775807] |
Int128 | [-170141183460469231731687303715884105728 : 170141183460469231731687303715884105727] |
无符号整型范围
名称 | 范围 |
UInt8 | [0 : 255] |
UInt16 | [0 : 65535] |
UInt32 | [0 : 4294967295] |
UInt64 | [0 : 18446744073709551615] |
UInt128 | [0 : 340282366920938463463374607431768211455] |
2.2 浮点型
Float32 - float
Float64 - double
建议尽可能以整数形式存储数据。例如,将固定精度的数字转换为整数值,如时间用毫秒为单位表示,因为浮点型进行计算时可能引起四舍五入的误差。
SELECT 1 - 0.9
┌───────minus(1, 0.9)─┐
│ 0.09999999999999998 │
└─────────────────────┘
与标准SQL相比,ClickHouse 支持以下类别的浮点数:
-- Inf – 正无穷
SELECT 0.5 / 0
┌─divide(0.5, 0)─┐
│ inf │
└────────────────┘
---Inf – 负无穷
┌─divide(-0.5, 0)─┐
│ -inf │
└─────────────────┘
--NaN – 非数字
SELECT 0 / 0
┌─divide(0, 0)─┐
│ nan │
└──────────────┘
2.3 Decimal
有符号的定点数,可在加、减和乘法运算过程中保持精度。对于除法,最低有效数字会被丢弃(不舍入)。
格式:Decimal(P,S)
P:代表精度,决定总位数(正数部分+小数部分),取值范围0-38
S - 规模。有效范围:[0:P],决定数字的小数部分中包含的小数位数。
十进制值范围
Decimal32(S) - ( -1 * 10^(9 - S),110^(9-S) )
Decimal64(S) - ( -1 * 10^(18 - S),110^(18-S) )
Decimal128(S) - ( -1 * 10^(38 - S),1*10^(38-S) )
精度变化的规则:
加法,减法:S = max(S1, S2)。 --最大值
乘法:S = S1 + S2。 – 两个精度相加
除法:S = S1 --被除数的
SELECT toDecimal32(2.3333, 4) AS x, x + toDecimal32(2.22, 2)
2.3333 4.5533
SELECT toDecimal32(2.3333, 4) AS x, x * toDecimal32(2.22, 2)
2.3333 5.179926
SELECT toDecimal32(2.3333, 4) AS x, x / toDecimal32(2.22, 2)
2.3333 1.0510
2.4 字符串
字符串可以任意长度的。它可以包含任意的字节集,包含空字节。因此,字符串类型可以代替其他 DBMSs 中的 VARCHAR、BLOB、CLOB 等类型。
2.5固定长度字符串
FixedString(N)
固定长度 N 的字符串,N 必须是严格的正自然数。当服务端读取长度小于 N 的字符串时候,通过在字符串末尾添加空字节来达到 N 字节长度。 当服务端读取长度大于 N 的字符串时候,将返回错误消息。
2.6UUID
通用唯一标识符(UUID)是一个16字节的数字,用于标识记录
如何生成
ClickHouse提供了 generateuidv4 函数
CREATE TABLE t_uuid (x UUID, y String) ENGINE=TinyLog
INSERT INTO t_uuid SELECT generateUUIDv4(), 'Example 1'
SELECT * FROM t_uuid
┌────────────────────────────────────x─┬─y─────────┐
│ 417ddc5d-e556-4d27-95dd-a34d84e46a50 │ Example 1 │
└──────────────────────────────────────┴───────────┘
INSERT INTO t_uuid (y) VALUES ('Example 2')
SELECT * FROM t_uuid
┌────────────────────────────────────x─┬─y─────────┐
│ 417ddc5d-e556-4d27-95dd-a34d84e46a50 │ Example 1 │
│ 00000000-0000-0000-0000-000000000000 │ Example 2 │
└──────────────────────────────────────┴───────────┘
2.7 枚举
包括 Enum8 和 Enum16 类型。Enum 保存 ‘string’= integer 的对应关系。在 ClickHouse 中,尽管用户使用的是字符串常量,但所有含有Enum 数据类型的操作都是按照包含整数的值来执行。这在性能方面比使用 String 数据类型更有效。
- Enum8 用 ‘String’= Int8 对描述。
- Enum16 用 ‘String’= Int16 对描述。
用法示例
创建一个带有一个枚举 Enum8('hello' = 1, 'world' = 2) 类型的列:
CREATE TABLE t_enum
(
x Enum8('hello' = 1, 'world' = 2)
)
ENGINE = TinyLog
这个x 列只能存储类型定义中列出的值:'hello'或'world'。如果您尝试保存任何其他值,ClickHouse 抛出异常。
:) INSERT INTO t_enum VALUES ('hello'), ('world'), ('hello')
INSERT INTO t_enum VALUES
Ok.
3 rows in set. Elapsed: 0.002 sec.
:) insert into t_enum values('a')
INSERT INTO t_enum VALUES
Exception on client:
Code: 49. DB::Exception: Unknown element 'a' for type Enum8('hello' = 1, 'world' = 2)
当您从表中查询数据时,ClickHouse 从 Enum 中输出字符串值
SELECT * FROM t_enum
┌─x─────┐
│ hello │
│ world │
│ hello │
└───────┘
如果需要看到对应行的数值,则必须将 Enum 值转换为整数类型。
SELECT CAST(x, 'Int8') FROM t_enum
┌─CAST(x, 'Int8')─┐
│ 1 │
│ 2 │
│ 1 │
└─────────────────┘
2.8 数组
Array(T):由 T 类型元素组成的数组。
T 可以是任意类型,包含数组类型。 但不推荐使用多维数组,ClickHouse 对多维数组的支持有限。例如,不能存储在 MergeTree 表中存储多维数组。
可以使用array函数来创建数组:array(T)
也可以使用方括号[]:ClickHouse能够自动推断数据类型
:) SELECT array(1, 2) AS x, toTypeName(x)
SELECT
[1, 2] AS x,
toTypeName(x)
┌─x─────┬─toTypeName(array(1, 2))─┐
│ [1,2] │ Array(UInt8) │
└───────┴─────────────────────────┘
1 rows in set. Elapsed: 0.002 sec.
:) SELECT [1, 2] AS x, toTypeName(x)
SELECT
[1, 2] AS x,
toTypeName(x)
┌─x─────┬─toTypeName([1, 2])─┐
│ [1,2] │ Array(UInt8) │
└───────┴────────────────────┘
1 rows in set. Elapsed: 0.002 sec.
2.9 元组
Tuple(T1, T2, …):元组,其中每个元素都有单独的类型。
创建元组
tuple(T1, T2, ...)
:) SELECT tuple(1,'a') AS x, toTypeName(x)
SELECT
(1, 'a') AS x,
toTypeName(x)
┌─x───────┬─toTypeName(tuple(1, 'a'))─┐
│ (1,'a') │ Tuple(UInt8, String) │
└─────────┴───────────────────────────┘
1 rows in set. Elapsed: 0.021 sec.
2.10 Date、DateTime
日期类型,用两个字节存储,表示从 1970-01-01 (无符号) 到当前的日期值。
2.11 布尔类型
从 https://github.com/ClickHouse/ClickHouse/commit/4076ae77b46794e73594a9f400200088ed1e7a6e 之后,有单独的类型来存储布尔值。
在此之前的版本,没有单独的类型来存储布尔值。可以使用 UInt8 类型,取值限制为 0 或 1。
表引擎
表引擎(即表的类型)决定了:
1)数据的存储方式和位置,写到哪里以及从哪里读取数据
2)支持哪些查询以及如何支持。
3)并发数据访问。
4)索引的使用(如果存在)。5)是否可以执行多线程请求。
6)数据复制参数。
ClickHouse的表引擎有很多,下面介绍其中几种,对其他引擎有兴趣的可以去查阅官方文档:https://clickhouse.yandex/docs/zh/operations/table_engines/
3.1 日志
3.1.1TinyLog
最简单的表引擎,用于将数据存储在磁盘上。每列都存储在单独的压缩文件中,写入时,数据将附加到文件末尾。该引擎没有并发控制
- 如果同时从表中读取和写入数据,则读取操作将抛出异常;
- 如果同时写入多个查询中的表,则数据将被破坏。
这种表引擎的典型用法是 write-once:首先只写入一次数据,然后根据需要多次读取。此引擎适用于相对较小的表(建议最多1,000,000行)。如果有许多小表,则使用此表引擎是适合的,因为它需要打开的文件更少。当拥有大量小表时,可能会导致性能低下。 不支持索引。
案例:创建一个TinyLog引擎的表并插入一条数据
:)create table t (a UInt16, b String) ENGINE=TinyLog;
:)insert into t (a, b) values (1, 'abc');
3.1.2 Log
Log 与 TinyLog 的不同之处在于,«标记» 的小文件与列文件存在一起。这些标记写在每个数据块上,并且包含偏移量,这些偏移量指示从哪里开始读取文件以便跳过指定的行数。这使得可以在多个线程中读取表数据。对于并发数据访问,可以同时执行读取操作,而写入操作则阻塞读取和其它写入。Log引擎不支持索引。同样,如果写入表失败,则该表将被破坏,并且从该表读取将返回错误。Log引擎适用于临时数据,write-once 表以及测试或演示目的。
3.1.3 StripeLog
该引擎属于日志引擎系列。请在日志引擎系列文章中查看引擎的共同属性和差异。在你需要写入许多小数据量(小于一百万行)的表的场景下使用这个引擎。
建表
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
column1_name [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
column2_name [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = StripeLog
查看建表请求的详细说明。写数据
StripeLog 引擎将所有列存储在一个文件中。对每一次 Insert 请求,ClickHouse 将数据块追加在表文件的末尾,逐列写入。
ClickHouse 为每张表写入以下文件:
- data.bin — 数据文件。
- index.mrk — 带标记的文件。标记包含了已插入的每个数据块中每列的偏移量。
StripeLog 引擎不支持 ALTER UPDATE 和 ALTER DELETE 操作。
读数据
带标记的文件使得 ClickHouse 可以并行的读取数据。这意味着 SELECT 请求返回行的顺序是不可预测的。使用 ORDER BY 子句对行进行排序
-- 建表
CREATE TABLE stripe_log_table
(
timestamp DateTime,
message_type String,
message String
)
ENGINE = StripeLog
--插入数据
INSERT INTO stripe_log_table VALUES (now(),'REGULAR','The first regular message')
INSERT INTO stripe_log_table VALUES (now(),'REGULAR','The second regular message'),(now(),'WARNING','The first warning message')
--我们使用两次 INSERT 请求从而在 data.bin 文件中创建两个数据块。ClickHouse 在查询数据时使用多线程。每个线程读取单独的数据块并在完成后独立的返回结果行。这样的结果是,大多数情况下,输出中块的顺序和输入时相应块的顺序是不同的。例如:
SELECT * FROM stripe_log_table
┌───────────timestamp─┬─message_type─┬─message────────────────────┐
│ 2019-01-18 14:27:32 │ REGULAR │ The second regular message │
│ 2019-01-18 14:34:53 │ WARNING │ The first warning message │
└─────────────────────┴──────────────┴────────────────────────────┘
┌───────────timestamp─┬─message_type─┬─message───────────────────┐
│ 2019-01-18 14:23:43 │ REGULAR │ The first regular message │
└─────────────────────┴──────────────┴───────────────────────────┘
--对结果排序(默认增序):
SELECT * FROM stripe_log_table ORDER BY timestamp
┌───────────timestamp─┬─message_type─┬─message────────────────────┐
│ 2019-01-18 14:23:43 │ REGULAR │ The first regular message │
│ 2019-01-18 14:27:32 │ REGULAR │ The second regular message │
│ 2019-01-18 14:34:53 │ WARNING │ The first warning message │
└─────────────────────┴──────────────┴────────────────────────────┘
3.2 Memory
内存引擎,数据以未压缩的原始形式直接保存在内存当中,服务器重启数据就会消失。读写操作不会相互阻塞,不支持索引。简单查询下有非常非常高的性能表现(超过10G/s)。
一般用到它的地方不多,除了用来测试,就是在需要非常高的性能,同时数据量又不太大(上限大概 1 亿行)的场景。一般就是测试和etl的中间表或者临时表了
3.3 Merge
Merge 引擎 (不要跟 MergeTree 引擎混淆) 本身不存储数据,但可用于同时从任意多个其他的表中读取数据。 读是自动并行的,不支持写入。读取时,那些被真正读取到数据的表的索引(如果有的话)会被使用。 Merge 引擎的参数:一个数据库名和一个用于匹配表名的正则表达式。
案例:先建t1,t2,t3三个表,然后用 Merge 引擎的 t 表再把它们链接起来。
:)create table t1 (id UInt16, name String) ENGINE=TinyLog;
:)create table t2 (id UInt16, name String) ENGINE=TinyLog;
:)create table t3 (id UInt16, name String) ENGINE=TinyLog;
:)insert into t1(id, name) values (1, 'first');
:)insert into t2(id, name) values (2, 'second');
:)insert into t3(id, name) values (3, 'i am in t3');
:)create table t (id UInt16, name String) ENGINE=Merge(currentDatabase(), '^t');
:) select * from t;
┌─id─┬─name─┐
│ 2 │ second │
└────┴──────┘
┌─id─┬─name──┐
│ 1 │ first │
└────┴───────┘
┌─id─┬─name───────┐
│ 3
│ i am in t3 │
└────┴────────────┘
3.4 MergeTree
Clickhouse 中最强大的表引擎当属 MergeTree (合并树)引擎及该系列(*MergeTree)中的其他引擎。
MergeTree 系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以以数据片段的形式一个接着一个的快速写入,数据片段在后台按照一定的规则进行合并。相比在插入时不断修改(重写)已存储的数据,这种策略会高效很多。
主要特点:
存储的数据按主键排序。
如果指定了 分区键 的话,可以使用分区。
支持数据副本。
支持数据采样
--建表
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 (CounterID, EventDate) 。如果没有使用 PRIMARY
KEY 显式指定的主键,ClickHouse 会使用排序键作为主键。如果不需要排序,可以使用 ORDER BY tuple()
- PARTITION BY — 分区键 。
要按月分区,可以使用表达式 toYYYYMM(date_column) ,这里的 date_column 是一个 Date
类型的列。这里该分区名格式会是 “YYYYMM” 这样。
- PRIMARY KEY - 主键,如果要设成 跟排序键不相同
- SAMPLE BY - 用于抽样的表达式,可选项。
如果要用抽样表达式,主键中必须包含这个表达式。例如: SAMPLE BY intHash32(UserID) ORDER BY
(CounterID, EventDate, intHash32(UserID))
- TTL - 指定行存储的持续时间并定义数据片段在硬盘和卷上的移动逻辑的规则列表,可选项。
表达式中必须存在至少一个 Date 或 DateTime 类型的列,比如:TTL date + INTERVAl 1 DAY规则的类型
有DELETE|TO DISK ‘xxx’|TO VOLUME ‘xxx’, 默认是delete,也可以指定移动到指定的磁盘或卷里,
- SETTINGS — 控制 MergeTree 行为的额外参数,可选项:
- index_granularity — 索引粒度。即索引中相邻『标记』间的数据行数。默认值,8192 。该列表中所有可用的参数可以从这里查看 MergeTreeSettings.h
- index_granularity_bytes — 索引粒度,以字节为单位,默认值: 10Mb。如果仅按数据行数限制索引粒度,请设置为0(不建议)。
- enable_mixed_granularity_parts — 启用或禁用通过 index_granularity_bytes 控制索引粒度的大小。在19.11版本之前, 只有 index_granularity 配置能够用于限制索引粒度的大小。当从大表(数十或数百兆)中查询数据时候,index_granularity_bytes 配置能够提升ClickHouse的性能。如果你的表内数据量很大,可以开启这项配置用以提升SELECT 查询的性能。
- use_minimalistic_part_header_in_zookeeper — 数据片段头在 ZooKeeper 中的存储方式。如果设置了use_minimalistic_part_header_in_zookeeper=1 ,ZooKeeper 会存储更少的数据。更多信息参考『服务配置参数』这章中的 设置描述 。
- min_merge_bytes_to_use_direct_io — 使用直接 I/O 来操作磁盘的合并操作时要求的最小数据量。合并数据片段时,ClickHouse 会计算要被合并的所有数据的总存储空间。如果大小超过了min_merge_bytes_to_use_direct_io 设置的字节数,则 ClickHouse 将使用直接 I/O 接口(O_DIRECT 选项)对磁盘读写。如果设置min_merge_bytes_to_use_direct_io = 0 ,则会禁用直接 I/O。默认值:10 *1024 * 1024 * 1024 字节。
- merge_with_ttl_timeout — TTL合并频率的最小间隔时间。默认值: 86400 (1 天)。
- write_fifinal_mark — 启用或禁用在数据片段尾部写入最终索引标记。默认值: 1(不建议更改)。
- storage_policy — 存储策略。 参见 使用具有多个块的设备进行数据存储.主要做冷热数据隔离存储的方式
MergeTree的存储结构
-- table
|-- 20180301_20180330_1_100_20
| |-- checksums.txt --二进制的校验文件,保存了余下文件的大小size和size的Hash值,用于快速校验文件的完整和正确性
| |-- columns.txt --明文的列信息文件
| |-- date.bin --压缩格式(默认LZ4)的数据文件,保存了原始数据。以列名.bin命名。
| |-- date.mrk2 --二进制的列字段标记文件,作用是把稀疏索引.idx文件和存数据的文件.bin联系起来。(由 Wide 或 Compact 格式存储格式决定)
| |-- id.bin
| |-- id.mrk2
| |-- name.bin
| |-- name.mrk2
| `-- primary.idx --二进制的一级索引文件,在建表的时候通过OrderBy或者PrimaryKey声明的稀疏索引。
|-- 20180601_20180629_101_200_20
| |-- checksums.txt
| |-- columns.txt
| |-- date.bin
| |-- date.mrk2
| |-- id.bin
| |-- id.mrk2
| |-- name.bin
| |-- name.mrk2
| `-- primary.idx
|-- detached
-- format_version.txt
数据片段可以以 Wide 或 Compact 格式存储。在 Wide 格式下,每一列都会在文件系统中存储为单独的文件,在 Compact 格式下所有列都存储在一个文件中。Compact 格式可以提高插入量少插入频率频繁时的性能。
[column].bin | data.bin
存放某一列的真实数据的文件,当采用wide part模式时会为每一列生成这样的文件,文件名就是列的名字。另外一种模式是compact part模式,这种模式下所有的列的数据放在一个文件data.bin里面。
数据存储格式由 min_bytes_for_wide_part 和 min_rows_for_wide_part 表引擎参数控制。如果数据片段中的字节数或行数少于相应的设置值,数据片段会以 Compact 格式存储,否则会以 Wide 格式存储。
在高本版中会有min_bytes_for_wide_part和min_rows_for_wide_part
min_bytes_for_wide_part
数据从合并存储(Compact)转成按列存储(Wide)的最小文件大小。
min_rows_for_wide_part
数据从合并存储(Compact)转成按列存储(Wide)的最小行数。
ALTER TABLE test.test_event_log MODIFY SETTING min_rows_for_wide_part = 5;
数据分区
数据是以分区目录的形式组织的,每个分区独立分开存储。这种形式,查询数据时,可以有效的跳过无用的数据文件 /var/lib/clickhouse/data/databses/table/分区
数据分区的规则
分区键的取值,生成分区ID,分区根据ID决定。根据分区键的数据类型不同,分区ID的生成目前有四种规则:
(1)不指定分区键
(2)使用整形
(3)使用日期类型 toYYYYMM(date)
(4)使用其他类型
分区目录的生成规则
partitionID_MinBlockNum_MaxBlockNum_Level
BlockNum是一个全局整型,从1开始,每当新创建一个分区目录,此数字就累加1。
MinBlockNum:最小数据块编号
MaxBlockNum:最大数据块编号
对于一个新的分区,MinBlockNum和MaxBlockNum的值相同
如:201905_1_1_0, 2019_06_2_2_0
Level:合并的层级,即某个分区被合并过得次数。不是全局的,而是针对某一个分区。
不同的批次写入数据属于同一分区,也会生成不同的目录,在之后的某个时刻再合并(写入后的10-15分钟),合并后的旧分区目录默认8分钟后删除。
索引
一级索引
文件:primary.idx
MergeTree的主键使用Primary Key定义,主键定义之后,MergeTree会根据index_granularity间隔(默认8192)为数据生成一级索引并保存至primary.idx文件中。这种方式是稀疏索引
稀疏索引
primary.idx文件的一级索引采用稀疏索引。稀疏索引占用空间小,所以primary.idx内的索引数据常驻内存,取用速度快!
稠密索引:每一行索引标记对应一行具体的数据记录
稀疏索引:每一行索引标记对应一段数据记录(默认索引粒度为8192)
索引的查询过程
按照index_granularity的间隔粒度,将一段完整的数据划分成多个小的数据段,小的数据段就是MarkRange,MarkRange与索引编号对应。其中每个 granule 中索引列的第一个记录将作为索引写入到 primary.idx
数据标记和索引区间是对齐的,根据索引区间的下标编号,就能找到数据标记—索引编号和数据标记数值相同
每一个[Column].bin都有一个[Column].mrk与之对应—.mrk文件记录数据在.bin文件中的偏移量
跳数索引
*MergeTree 系列的表可以指定跳数索引。 跳数索引是指数据片段按照粒度(建表时指定的index_granularity)分割成小块后,将上述SQL的granularity_value数量的小块组合成一个大的块,对这些大块写入索引信息,这样有助于使用where筛选时跳过大量不必要的数据,减少SELECT需要读取的数据量。
此索引在 CREATE 语句的列部分里定义。
INDEX index_name expr TYPE type(...) GRANULARITY granularity_value
CREATE TABLE table_name
(
u64 UInt64,
i32 Int32,
s String,
...
INDEX a (u64 * i32, s) TYPE minmax GRANULARITY 3,
INDEX b (u64 * length(s)) TYPE set(1000) GRANULARITY 4
) ENGINE = MergeTree()
...
上例中的索引能让 ClickHouse 执行下面这些查询时减少读取数据量。
SELECT count() FROM table WHERE s < 'z'
SELECT count() FROM table WHERE u64 * i32 == 10 AND u64 * length(s) >= 1234
数据存储
表由按主键排序的数据 片段 组成。当数据被插入到表中时,会分成数据片段并按主键的字典序排序。例如,主键是 (CounterID, Date) 时,片段中数据按 CounterID 排序,具有相同 CounterID 的部分按 Date 排序。
不同分区的数据会被分成不同的片段,ClickHouse 在后台合并数据片段以便更高效存储。不会合并来自不同分区的数据片段。这个合并机制并不保证相同主键的所有行都会合并到同一个数据片段中。
ClickHouse 会为每个数据片段创建一个索引文件,索引文件包含每个索引行(『标记』)的主键值。索引行号定义为 n * index_granularity 。最大的 n 等于总行数除以 index_granularity 的值的整数部分。对于每列,跟主键相同的索引行处也会写入『标记』。这些『标记』让你可以直接找到数据所在的列。
按列存储
在MergeTree中数据按列存储,具体到每个列字段,都拥有一个.bin数据文件,是最终存储数据的文件。 按列存储的好处: 1、更好的压缩 2、最小化数据扫描范围
MergTree的TTL
TTL:time to live 数据存活时间。TTL既可以设置在表上,也可以设置在列上。TTL指定的时间到期后则删除相应的表或列,如果同时设置了TTL,则根据先过期时间删除相应数据。
TTL 表达式的计算结果必须是 日期 或 日期时间 类型的字段。
TTL time_column
TTL time_column + interval
要定义
interval, 需要使用 时间间隔 操作符。SECOND MINUTE HOUR DAY WEEK MONTH QUARTER YEAR
TTL date_time + INTERVAL 1 MONTH
TTL date_time + INTERVAL 15 HOUR
列 TTL
当列中的值过期时, ClickHouse会将它们替换成该列数据类型的默认值。如果数据片段中列的所有值均已过期,则ClickHouse 会从文件系统中的数据片段中删除此列。TTL子句不能被用于主键字段。
创建表时指定 TTL
CREATE TABLE example_table
(
d DateTime,
a Int TTL d + INTERVAL 1 MONTH,
b Int TTL d + INTERVAL 1 MONTH,
c String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d;
为表中已存在的列字段添加 TTL
ALTER TABLE example_table MODIFY COLUMN c String TTL d + INTERVAL 1 DAY;
修改列字段的TTL
ALTER TABLE example_table MODIFY COLUMN c String TTL d + INTERVAL 1 MONTH;
表 TTL
TTL expr
[DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'][, DELETE|TO DISK 'aaa'|TO VOLUME 'bbb'] ...
[WHERE conditions]
[GROUP BY key_expr [SET v1 = aggr_func(v1) [, v2 = aggr_func(v2) ...]] ]
TTL 规则的类型紧跟在每个 TTL 表达式后面,它会影响满足表达式时(到达指定时间时)应当执行的操作:
- DELETE - 删除过期的行(默认操作);
- TO DISK ‘aaa’ - 将数据片段移动到磁盘 aaa;
- TO VOLUME ‘bbb’ - 将数据片段移动到卷 bbb.
- GROUP BY - 聚合过期的行
使用WHERE从句,您可以指定哪些过期的行会被删除或聚合(不适用于移动)。
GROUP BY表达式必须是表主键的前缀。如果某列不是GROUP BY表达式的一部分,也没有在SET从句显示引用,结果行中相应列的值是随机的(就好像使用了any函数)。
创建时指定 TTL
CREATE TABLE example_table
(
d DateTime,
a Int
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d
TTL d + INTERVAL 1 MONTH [DELETE],
d + INTERVAL 1 WEEK TO VOLUME 'aaa',
d + INTERVAL 2 WEEK TO DISK 'bbb';
修改表的 TTL
ALTER TABLE example_table MODIFY TTL d + INTERVAL 1 DAY;
创建一张表,设置一个月后数据过期,这些过期的行中日期为星期一的删除:
CREATE TABLE table_with_where
(
d DateTime,
a Int
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d
TTL d + INTERVAL 1 MONTH DELETE WHERE toDayOfWeek(d) = 1;
删除数据
ClickHouse 在数据片段合并时会删除掉过期的数据。
当ClickHouse发现数据过期时, 它将会执行一个计划外的合并。要控制这类合并的频率, 您可以设置 merge_with_ttl_timeout。如果该值被设置的太低, 它将引发大量计划外的合并,这可能会消耗大量资源。
如果在两次合并的时间间隔中执行 SELECT 查询, 则可能会得到过期的数据。为了避免这种情况,可以在 SELECT 之前使用 OPTIMIZE 。(所有关于ck自动合并,删除等操作的 都是有延时性的,有一个固定时间去操作的,为了避免拿到过期数据 可以加上关键字强制执行)
未完 待续
ReplacingMergeTree
SummingMergeTree
mysql
hive
kafka
高级函数
分片、副本
物化视图…