Table engines
table engine 决定:
- 数据存储的方式和地点:将数据写入何处,以及从何处读取数据
- 支持哪些查询,如何支持的
- 并发数据访问
- 如果存在索引,使用
- 是否可以执行多线程请求
- 数据复制
- 当读取数据时,引擎只需要提取必要的列集。但是,在某些情况下,查询可能在表引擎中部分处理
注意,对于大多数重要任务,应该使用来自MergeTree家族的引擎
TinyLog
最简单的表引擎,它将数据存储在磁盘上。每一列都存储在一个单独的压缩文件中。在编写时,数据被追加到文件的末尾
并发数据访问不受任何限制:
- 如果您正在从一个表中读取并在另一个查询中写入它,会报错
- 如果您同时在多个查询中写入表格,数据将被破坏
使用这个表的典型方法是写一次:只写一次数据,然后根据需要多次读取数据。查询是在单个流中执行的。换句话说,这个引擎是为相对较小的表准备的(建议最多为1百万行)。如果您有许多小的表,那么使用这个表引擎是有意义的,因为它比日志引擎更简单(需要打开的文件更少)。当您拥有大量的小表时,这种情况就会导致效率低下,另外不支持索引
Yandex。在小批量处理的中间数据中,使用的是TinyLog表格。
Log
日志与TinyLog的不同之处在于,一个小的“标记”文件与列文件共存。这些标记都写在每个数据块上,并包含表示从哪里开始读取文件的偏移量,以便跳过指定的行数。这使得在多个线程中读取表数据成为可能。对于并发的数据访问,读取操作可以同时执行,而写操作块读取和彼此读取。日志引擎不支持索引。类似地,如果对一个表的写入失败的话,表格就会被破坏,并且从它读取数据会返回一个错误。日志引擎适用于临时数据、写一次表,以及用于测试或演示的目的
Memory
内存引擎以未压缩的形式存储在RAM中的数据。数据的存储方式与读取时接收到的数据完全相同。换句话说,从这个表中阅读是完全免费的。并发数据访问是同步的。锁是短的:读写操作不会相互阻塞。不支持索引。阅读是并行。在简单的查询中可以达到最大的生产力(超过10 gb/秒),因为没有从磁盘读取、解压或反序列化数据。(我们应该注意到,在许多情况下,MergeTree引擎的生产率几乎同样高。)当重新启动服务器时,数据从表中消失,表变为空。通常,使用这个表引擎是不合理的。但是,它可以用于测试,以及在相对较少的行中需要最大速度的任务(大约为100万)
系统使用内存引擎为带有外部查询数据的临时表(请参阅“处理查询的外部数据”一节),以及实现global IN(请参阅“操作符”部分)。
MergeTree
MergeTree引擎支持主键和日期的索引,并提供实时更新数据的可能性。这是ClickHouse中最先进的表引擎。不要将它与合并引擎混淆
引擎接受参数:包含日期的日期类型列的名称、一个采样表达式(可选)、一个定义表的主键的元组和索引粒度。例如:
Example without sampling support:
MergeTree(EventDate, (CounterID, EventDate), 8192)
Example with sampling support:
MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID)), 8192)
一个MergeTree类型表必须有一个包含日期的单独列。在本例中,它是“EventDate”列。日期列的类型必须是“DATE”(不是“DateTime”)
主键可以是任何表达式的tuple(通常这只是一个列的元组),也可以是单个表达式。
要检查ClickHouse在执行查询时是否可以使用该索引,请使用settings forceindexbydate和forceprimarykey。
您可以使用单个大表,并在小块中不断地向其添加数据——这就是MergeTree的目的
在MergeTree家族中,所有类型的表都可以进行数据复制
Custom partitioning key
自定义分区键:从版本1.1.54310开始,您可以在MergeTree家族中创建任何分区表达式(不仅仅是按月划分)
分区键可以是表列的表达式,也可以是此类表达式的元组(类似于主键)。分区键可以省略。在创建表格时,使用新的语法指定引擎描述中的分区键:
ENGINE [=] Name(...) [PARTITION BY expr] [ORDER BY expr] [SAMPLE BY expr] [SETTINGS name=value, ...]
对于MergeTree的表,分区表达式是在分区之后指定的,按顺序排序后的主键,采样后的采样键,以及设置可以指定索引粒度(可选;默认值是8192),以及其他来自MergeTreeSettings.h的设置。
举例:
ENGINE = ReplicatedCollapsingMergeTree('/clickhouse/tables/name', 'replica1', Sign)
PARTITION BY (toMonday(StartDate), EventType)
ORDER BY (CounterID, StartDate, intHash32(UserID))
SAMPLE BY intHash32(UserID)
要在ALTER PARTITION命令中指定一个分区,请指定分区表达式(或tuple)的值。常量和常量表达式是受支持的。例子:
ALTER TABLE table DROP PARTITION (toMonday(today()), 1)
用事件类型1删除当前星期的分区。对优化查询也是如此。要指定非分区表中的唯一分区,请指定分区tuple()。
分区数字含义:
之前:2014031720140323220(最小数据-最大数据-最小块数-最大块-级数)
之后:201403220(分区ID-最小块的数量-最大块级的数量)
分区ID是它的字符串标识符(如果可能的话,是人类可读的),它用于文件系统和ZooKeeper中的数据部件的名称。您可以在ALTER查询中指定分区键。例如:分区键toYYYYMM(EventDate);ALTER可以指定分区201710或分区ID“201710”
ReplacingMergeTree
这个引擎表与MergeTree不同,因为它删除了具有相同主键值的重复条目。
表引擎的最后一个可选参数是“version”列。当合并时,它会将同一主键值的所有行缩减为一行。如果指定了版本列,那么它就会留下具有最高版本的行;否则,它将留下最后一行。
ReplacingMergeTree(EventDate, (OrderID, EventDate, BannerID, ...), 8192, ver)
版本列的类型必须是 UInt
相关的 Date
, or DateTime
.
注意,数据只在合并过程中被重复。合并在一个未知的时间发生在后台,所以你不能计划它。一些数据可能仍未被处理
尽管您可以使用优化查询来运行计划外的合并,但不要指望使用它,因为优化查询将读取和写入大量数据。
因此,替换mergetree适合于在背景中清除重复数据,以节省空间,但不能保证重复数据的缺失
SummingMergeTree
这个引擎与MergeTree不同,因为它在合并的时候会收集数据
SummingMergeTree(EventDate, (OrderID, EventDate, BannerID, ...), 8192)
列总数是隐式的。当合并时,所有具有相同主键值的行(在本例中,OrderId、EventDate、BannerID……)都有它们的值,它们的值都不是主键的一部分。
SummingMergeTree(EventDate, (OrderID, EventDate, BannerID, ...), 8192, (Shows, Clicks, Cost, ...))
列总数是显式设置的(最后一个参数——显示、单击、开销……)。当合并时,所有具有相同主键值的行都在指定的列中有它们的值。指定的列也必须是数字的,并且不能是主键的一部分。
对于不属于主键的其他行来说,当合并时选中的第一个值将被选中。
这个表引擎并不是特别有用。记住,在保存预聚合数据时,系统的一些优点就会丢失。
AggregatingMergeTree
这个引擎与MergeTree不同,因为合并将存储在表中的聚合功能的状态合并为具有相同主键值的行。
为了使之工作,它使用聚合功能数据类型和聚合函数的-State和-Merge修饰符。
注意,在大多数情况下,使用聚合型mergetree是不合理的,因为查询可以在非聚合数据上高效地运行
CollapsingMergeTree
这个引擎是专门为Yandex.Metrica使用的
它与MergeTree不同,因为它允许自动删除,或者在合并时折叠某些行
Yandex.Metrica有正常的日志(比如命中日志)和更改日志。更改日志用于增量地计算不断变化的数据的统计数据。例子是会话更改的日志,或者记录用户历史记录的日志。在Yandex.Metrica中,会话不断变化。例如,每个会话的点击数增加。我们将任何对象的变更称作一对(?旧值,新值)。如果对象被创建,旧的值可能会丢失。如果对象被删除,新的值可能会丢失。如果对象被改变了,但是之前存在并且没有被删除
CollapsingMergeTree(EventDate, (CounterID, EventDate, intHash32(UniqID), VisitID), 8192, Sign)
这里,Sign是一列包含-1代表“旧”值,1代表“新”值
当合并时,每组连续相同的主键值(用于排序数据的列)被简化为不多于一行,列值“signcolumn=-1”(“负行”),而不多于一行与列值“signcolumn=1”(“正行”)。换句话说,来自变更日志的条目会collapsing
Data replication
复制只支持MergeTree家族中的表。复制工作在单个表的级别上,而不是整个服务器。服务器可以同时存储复制的和非复制的表。
插入和修改是复制的(为了获得更多信息,请参阅ALTER)。压缩数据是复制的,而不是查询文本。CREATE、DROP、ATTACH、DETACH和rename查询 这些不是复制的。换句话说,它们属于一个单独的服务器。CREATE TABLE查询在运行查询的服务器上创建一个新的可复制表。如果这个表已经存在于其他服务器上,它会添加一个新的副本。DROP TABLE查询会删除运行查询的服务器上的副本。RENAME查询重命名一个副本上的表。换句话说,复制的表可以有不同的n
复制是异步的和多主机的。插入查询(以及ALTER)可以发送到任何可用的服务器。数据被插入到这个服务器上,然后发送到其他服务器。因为它是异步的,最近插入的数据在其他副本上出现延迟。如果副本的一部分不可用,那么当它们可用时,就会写入它们的数据。如果一个副本可用,那么延迟就是在网络上传输压缩数据块所需的时间
如果您将一批数据写到一个副本,并且带有该数据的服务器在数据有时间到达其他副本之前停止存在,那么这些数据就会丢失。
在复制期间,只有插入的源数据通过网络传输。进一步的数据转换(合并)以相同的方式在所有副本上进行协调和执行。这将最小化网络的使用,这意味着当副本驻留在不同的数据中心时,复制就可以很好地工作。(注意,在不同的数据中心中复制数据是复制的主要目标。)
Creating replicated tables
Recovery after failures
如果报异常,系统将检查本地文件系统中的数据集是否与预期的数据集匹配(ZooKeeper存储此信息)。如果有轻微的不一致性,系统通过与副本同步数据来解决它们。
如果系统检测到损坏的数据部分(文件大小错误)或未识别的部分(部分写入文件系统,但没有在ZooKeeper中记录),则会将它们移动到“分离”子目录(它们没有被删除)。任何丢失的部件都是从副本中复制的
注意,ClickHouse不执行任何破坏性的操作,比如自动删除大量数据。
如果本地数据与预期的数据相差太多,就会触发一个安全机制。服务器在日志中输入这个,并拒绝启动。这样做的原因是,这个案例可能表明了一个配置错误,例如,如果一个碎片上的副本被意外地配置为一个不同的碎片上的副本。但是,这个机制的阈值设置得相当低,这种情况可能发生在正常的故障恢复过程中。在这种情况下,数据是通过“按下一个按钮”自动恢复的
Recovery after complete data loss
如果所有的数据和元数据从一个服务器中消失,请按照以下步骤进行恢复:
在服务器上安装ClickHouse。如果您使用,在配置文件中正确地定义置换,其中包含了切分标识符和副本。
如果你有unreplicated表必须手动复制服务器,从副本复制他们的数据(在目录/var/lib/clickhouse/data/db_name/table_name/)
复制表定义位于/var/lib/clickhouse/metadata/复制品。如果一个shard或副本标识符在表定义中显式地定义,那么就对它进行更正,以便它对应于这个副本。(另外,启动服务器,并让所有的附加表查询,应该是在/var/lib/clickhouse/metadata/ . sql文件。)
开始恢复,创建管理员节点/ path_to_table / replica_name /标志/ force_restore_data与任何内容,或运行该命令恢复所有复制表:sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data
在恢复期间对网络带宽没有限制。如果您同时恢复许多副本,请记住这一点。
Converting from MergeTree to ReplicatedMergeTree
如果数据在不同的副本上有所不同,首先要同步它,或者删除所有副本上的数据,除了一个副本
Converting from ReplicatedMergeTree to MergeTree
创建一个具有不同名称的MergeTree表。将复制的mergetree表数据中的所有数据移动到新表的数据目录中。然后删除复制的mergetree表并重新启动服务器
- 删除对应的. sql文件元数据目录
- 在ZooKeeper(/pathtotable/replicaname)中删除相应的路径。
在此之后,您可以启动服务器,创建一个MergeTree表,将数据移动到它的目录,然后重新启动服务器。
Recovery when metadata in the ZooKeeper cluster is lost or damaged
如果ZooKeeper的数据丢失或损坏,可以通过将数据移动到上面描述的未复制表来保存数据。
如果在其他副本上有相同的部分,则将它们添加到其上的工作集中。如果没有,那么这些部件将从拥有它们的副本中下载
Distributed
分布式:
分布式引擎不存储数据本身,但允许在多个服务器上进行分布式查询处理。阅读是自动并行化。在读取期间,如果有的话,就使用远程服务器上的表索引。分布式引擎接受参数:服务器配置文件中的集群名称、远程数据库的名称、远程表的名称以及(可选的)分片键
Distributed(logs, default, hits[, sharding_key])
从默认情况下,将从logs集群中的所有服务器读取数据。hits位于集群中的每个服务器上。数据不仅是读取的,而且在远程服务器上进行了部分处理(在一定程度上,这是可能的)。例如,对于GROUP BY的查询,数据将聚合在远程服务器上,聚合函数的中间状态将被发送到请求者服务器。然后数据将被进一步聚合
有两种方法可以将数据写入集群:
首先,您可以定义哪些服务器来编写哪些数据,并在每个shard上直接执行写操作。换句话说,在分布式表“查看”的表中执行插入操作。这是最灵活的解决方案——您可以使用任何切分方案,由于主题领域的需求,这可能是非琐碎的。这也是最优的解决方案,因为数据可以完全独立地写入不同的切分。
其次,您可以在一个分布式表中执行插入操作。在这种情况下,表将把插入的数据分布到服务器本身。为了将其写入分布式表,它必须有一个分片键集(最后一个参数)。另外,如果只有一个切分,则写操作没有指定分片键,因为它在本例中没有任何意义。
每个碎片可以在配置文件中定义一个权重。在默认情况下,权重等于1。数据分布在碎片中,与碎片的权重成比例。例如,如果有两个切分,第一个的权重为9,而第二个则为10,那么第一个将被发送9/19部分行,第二个将被发送10/19。
每个碎片都可以在配置文件中定义“internal_replication制”参数。
如果这个参数被设置为true,那么write操作会选择第一个健康的副本并将数据写入它。如果分布式表“查看”复制的表,那么就使用这种替代方法。换句话说,要编写数据的表将复制它们本身。
如果它被设置为“false”(缺省值),则将数据写入所有副本。从本质上说,这意味着分布式表复制数据本身。这比使用复制表更糟糕,因为副本的一致性没有被检查,随着时间的推移,它们将包含略有不同的数据。
- 查询使用一个特定的键来连接数据(IN或JOIN)。如果数据被这个键分割,您可以使用local IN或JOIN代替GLOBAL IN或GLOBAL JOIN,后者更有效率
- 大量的服务器(数百个或更多)使用大量的小查询(单个客户的查询——网站、广告客户或合作伙伴)。为了使小查询不影响整个集群,在单个shard上为单个客户机定位数据是有意义的。或者,正如我们在Yandex中所做的那样。您可以设置双向分片:将整个集群划分为“层”,其中一个层可能由多个切分组成。单个客户端的数据位于单个层上,但是可以根据需要将碎片添加到一个层中,数据是随机分布的。
数据是异步写的。对于分布式表的插入,数据块只写到本地文件系统中。数据会尽快发送到后台的远程服务器。你应该通过检查文件的列表(数据等待发送)表目检查数据是否发送成功
如果服务器停止存在或在插入到分布式表之后进行了一次粗略的重新启动(例如,设备故障),插入的数据可能会丢失。如果在表目录中检测到损坏的数据部分,那么它就会被转移到“损坏的”子目录中,不再使用
Merge
合并引擎(不要与MergeTree混淆)不存储数据本身,但允许同时读取任意数量的其他表。阅读是自动并行化。不支持写入表。在读取时,如果存在的话,将使用正在读取的表的索引。合并引擎接受参数:数据库名称和表的正则表达式。
Merge(hits, '^WatchLog')
数据将从“hits”数据库中的表中读取,这些表的名称与正则表达式匹配,正则表达式大小写敏感
除了数据库名称,您可以使用一个返回字符串的常量表达式。例如,currentDatabase()
使用合并引擎的典型方式是使用大量的TinyLog表,就像使用单个表一样。
Virtual columns
虚拟列:虚拟列是表引擎提供的列,而不管表定义如何。换句话说,这些列不是在CREATE TABLE中指定的,但是它们是可访问的,供选择
虚拟列与普通列的不同之处如下:
- 它们没有在表定义中指定
- 数据不能添加到INSERT中
- 当使用INSERT而不指定列的列表时,虚拟列将被忽略
- 当使用星号(SELECT)时,不会选择它们
- 虚拟列没有显示在SHOW CREATE TABLE和DESC表查询中
Buffer
缓存:
缓冲数据以写入RAM,定期将其刷新到另一个表。在读取操作期间,数据从缓冲区和另一个表同时读取。
Buffer(database, table, num_layers, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes)
引擎参数:数据库、表——将数据刷新到的表。除了数据库名称,您可以使用一个返回字符串的常量表达式。num_layers——并行性层。从物理上说,表将被表示为独立缓冲区的“num_layers”。推荐值:16。mintime、maxtime、minrows、maxrows、minbytes和maxbytes都是用于从缓冲区刷新数据的条件。
如果满足所有“最小”条件或至少一个“最大”条件,则从缓冲区刷新数据并写入目标表。从第一次写入到缓冲区的瞬间,从秒到秒的时间。minrows,maxrows——缓冲区中的行数的条件。最小字节,maxbytes——缓冲区中字节数的条件。
在写操作期间,数据被插入到一个“numlayers”的随机缓冲区。或者,如果插入的数据部分足够大(大于maxrows或maxbytes),则直接写到目的地表,省略缓冲区。
例如:
CREATE TABLE merge.hits_buffer AS merge.hits ENGINE = Buffer(merge, hits, 16, 10, 100, 10000, 1000000, 10000000, 100000000)
创建一个merge。与“合并”相同结构的hitsbuffer表。点击并使用缓冲引擎。当写到这个表时,数据会在RAM中进行缓冲,然后写入“merge”。打表。16个缓冲区创建。如果超过100秒,或者已经写了100 MB的数据,或者100 MB的数据被写入,那么每一个数据都将被刷新。或者,如果同时10秒已经过去,并且已经写了1 000行和10 MB的数据
。如果只写一行,100秒后会被刷新。如果已经写了很多行,数据将很快刷新。
当服务器停止时,使用DROP TABLE或分离表,缓冲数据也会被刷新到目标表。
您可以为数据库和表名设置单引号的空字符串。这表明没有目标表。在这种情况下,当达到数据刷新条件时,缓冲区就会被清空。这对于保持内存中的数据窗口是很有用的。
当从缓冲表中读取数据时,无论是从缓冲区还是从目标表(如果有的话)都要处理数据。注意,缓冲表不支持索引。换句话说,缓冲区中的数据被完全扫描,对于大型缓冲区来说,这可能很慢。(对于下级表中的数据,它所支持的索引将被使用。
如果服务器异常重启,缓冲区中的数据就丢失了。如果您需要为下级表和缓冲表运行ALTER,我们建议首先删除缓冲表,为下级表运行ALTER,然后再次创建缓冲表。如果缓冲表中的列集与从属表中的列集不匹配,则插入两个表中的列子集。
当向缓冲区中添加数据时,其中一个缓冲区被锁定。如果从表中同时执行读操作,则会导致延迟。