ClickHouse-3引擎


引擎

  • 数据库引擎
  • index
  • 表引擎

数据库引擎

数据库引擎允许您处理数据表。

默认情况下,ClickHouse使用Atomic数据库引擎。它提供了可配置的table engines和SQL dialect。

您还可以使用以下数据库引擎:

  • MySQL

  • MaterializeMySQL

  • Lazy

  • Atomic

  • PostgreSQL

  • MaterializedPostgreSQL

  • Replicated

  • SQLite

MaterializedMySQL

这是一个实验性的特性,不应该在生产中使用。

创建ClickHouse数据库,包含MySQL中所有的表,以及这些表中的所有数据。

ClickHouse服务器作为MySQL副本工作。它读取binlog并执行DDL和DML查询。

这个功能是实验性的。

创建数据库?

CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MaterializeMySQL('host:port', ['database' | database], 'user', 'password') [SETTINGS ...]

MySQL服务器端配置

为了MaterializeMySQL正确的工作,有一些强制性的MySQL侧配置设置应该设置:

  • default_authentication_plugin = mysql_native_password,因为MaterializeMySQL只能使用此方法授权。
  • gtid_mode = on,因为要提供正确的MaterializeMySQL复制,基于GTID的日志记录是必须的。注意,在打开这个模式On时,你还应该指定enforce_gtid_consistency = on

虚拟列?

当使用MaterializeMySQL数据库引擎时,ReplacingMergeTree表与虚拟的_sign_version列一起使用。

  • _version — 同步版本。 类型UInt64.
  • _sign — 删除标记。类型 Int8. Possible values:
    • 1 — 行不会删除,
    • -1 — 行被删除。

支持的数据类型?

MySQLClickHouse
TINY Int8
SHORT Int16
INT24 Int32
LONG UInt32
LONGLONG UInt64
FLOAT Float32
DOUBLE Float64
DECIMAL, NEWDECIMAL Decimal
DATE, NEWDATE Date
DATETIME, TIMESTAMP DateTime
DATETIME2, TIMESTAMP2 DateTime64
ENUM Enum
STRING String
VARCHAR, VAR_STRING String
BLOB String
BINARY FixedString

不支持其他类型。如果MySQL表包含此类类型的列,ClickHouse抛出异常"Unhandled data type"并停止复制。

Nullable已经支持

使用方式?

兼容性限制?

除了数据类型的限制外,与MySQL数据库相比,还存在一些限制,在实现复制之前应先解决这些限制:

  • MySQL中的每个表都应该包含PRIMARY KEY

  • 对于包含ENUM字段值超出范围(在ENUM签名中指定)的行的表,复制将不起作用。

DDL查询?

MySQL DDL查询转换为相应的ClickHouse DDL查询(ALTER, CREATE, DROP, RENAME)。如果ClickHouse无法解析某个DDL查询,则该查询将被忽略。

Data Replication?

MaterializeMySQL不支持直接INSERTDELETEUPDATE查询. 但是,它们是在数据复制方面支持的:

  • MySQL的INSERT查询转换为INSERT并携带_sign=1.

  • MySQL的DELETE查询转换为INSERT并携带_sign=-1.

  • MySQL的UPDATE查询转换为INSERT并携带_sign=-1INSERT_sign=1.

查询MaterializeMySQL表?

SELECT查询MaterializeMySQL表有一些细节:

  • 如果_versionSELECT中没有指定,则使用FINAL修饰符。所以只有带有MAX(_version)的行才会被选中。

  • 如果_signSELECT中没有指定,则默认使用WHERE _sign=1。因此,删除的行不会包含在结果集中。

  • 结果包括列中的列注释,因为它们存在于SQL数据库表中。

Index Conversion?

MySQL的PRIMARY KEYINDEX子句在ClickHouse表中转换为ORDER BY元组。

ClickHouse只有一个物理顺序,由ORDER BY子句决定。要创建一个新的物理顺序,使用materialized views。

表重写?

表覆盖可用于自定义ClickHouse DDL查询,从而允许您对应用程序进行模式优化。这对于控制分区特别有用,分区对MaterializedMySQL的整体性能非常重要。

这些是你可以对MaterializedMySQL表重写的模式转换操作:

  • 修改列类型。必须与原始类型兼容,否则复制将失败。例如,可以将UInt32列修改为UInt64,不能将 String 列修改为 Array(String)
  • 修改 column TTL.
  • 修改 column compression codec.
  • 增加 ALIAS columns.
  • 增加 skipping indexes
  • 增加 projections. 请注意,当使用 SELECT ... FINAL (MaterializedMySQL默认是这样做的) 时,预测优化是被禁用的,所以这里是受限的, INDEX ... TYPE hypothesis [在v21.12的博客文章中描述]](https://clickhouse.com/blog/en/2021/clickhouse-v21.12-released/)可能在这种情况下更有用。
  • 修改 PARTITION BY
  • 修改 ORDER BY
  • 修改 PRIMARY KEY
  • 增加 SAMPLE BY
  • 增加 table TTL

Notes

  • 带有_sign=-1的行不会从表中物理删除。
  • MaterializeMySQL引擎不支持级联UPDATE/DELETE查询。
  • 复制很容易被破坏。
  • 禁止对数据库和表进行手工操作。
  • MaterializeMySQL受optimize_on_insert设置的影响。当MySQL服务器中的表发生变化时,数据会合并到MaterializeMySQL数据库中相应的表中。

使用示例?

MySQL操作:

mysql> CREATE DATABASE db;
mysql> CREATE TABLE db.test (a INT PRIMARY KEY, b INT);
mysql> INSERT INTO db.test VALUES (1, 11), (2, 22);
mysql> DELETE FROM db.test WHERE a=1;
mysql> ALTER TABLE db.test ADD COLUMN c VARCHAR(16);
mysql> UPDATE db.test SET c='Wow!', b=222;
mysql> SELECT * FROM test;

ClickHouse中的数据库,与MySQL服务器交换数据:

创建的数据库和表:

CREATE DATABASE mysql ENGINE = MaterializeMySQL('localhost:3306', 'db', 'user', '***');
SHOW TABLES FROM mysql;

然后插入数据:

SELECT * FROM mysql.test;

删除数据后,添加列并更新:

SELECT * FROM mysql.test;

Engine参数

  • host:port — PostgreSQL服务地址
  • database — PostgreSQL数据库名
  • user — PostgreSQL用户名
  • password — 用户密码

设置?

  • materialized_postgresql_max_block_size

  • materialized_postgresql_tables_list

  • materialized_postgresql_allow_automatic_update

CREATE DATABASE database1
ENGINE = MaterializedPostgreSQL('postgres1:5432', 'postgres_database', 'postgres_user', 'postgres_password')
SETTINGS materialized_postgresql_max_block_size = 65536,
materialized_postgresql_tables_list = 'table1,table2,table3';

SELECT * FROM database1.table1;

总是先检查主键。如果不存在,则检查索引(定义为副本标识索引)。 如果使用index作为副本标识,则表中必须只有一个这样的索引。 你可以用下面的命令来检查一个特定的表使用了什么类型:

postgres# SELECT CASE relreplident
WHEN 'd' THEN 'default'
WHEN 'n' THEN 'nothing'
WHEN 'f' THEN 'full'
WHEN 'i' THEN 'index'
END AS replica_identity
FROM pg_class
WHERE oid = 'postgres_table'::regclass;

引擎参数

  • host:port — MySQL服务地址
  • database — MySQL数据库名称
  • user — MySQL用户名
  • password — MySQL用户密码

支持的数据类型?

MySQLClickHouse
UNSIGNED TINYINT UInt8
TINYINT Int8
UNSIGNED SMALLINT UInt16
SMALLINT Int16
UNSIGNED INT, UNSIGNED MEDIUMINT UInt32
INT, MEDIUMINT Int32
UNSIGNED BIGINT UInt64
BIGINT Int64
FLOAT Float32
DOUBLE Float64
DATE Date
DATETIME, TIMESTAMP DateTime
BINARY FixedString

其他的MySQL数据类型将全部都转换为String.

Nullable已经支持

全局变量支持?

为了更好地兼容,您可以在SQL样式中设置全局变量,如@@identifier.

支持这些变量:

  • version
  • max_allowed_packet

!!! warning "警告" 到目前为止,这些变量是存根,并且不对应任何内容。

示例:

SELECT @@version;

ClickHouse中的数据库,与MySQL服务器交换数据:

CREATE DATABASE mysql_db ENGINE = MySQL('localhost:3306', 'test', 'my_user', 'user_password')
┌─name─────┐
│ default │
│ mysql_db │
│ system │
└──────────┘
┌─name─────────┐
│ mysql_table │
└──────────────┘
┌─int_id─┬─value─┐
│ 1 │ 2 │
└────────┴───────┘
SELECT * FROM mysql_db.mysql_table

使用方式?

Table UUID?

数据库Atomic中的所有表都有唯一的UUID,并将数据存储在目录/clickhouse_path/store/xxx/xxxyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy/,其中xxxyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy是该表的UUID。

通常,UUID是自动生成的,但用户也可以在创建表时以相同的方式显式指定UUID(不建议这样做)。可以使用 show_table_uuid_in_table_create_query_if_not_nil设置。显示UUID的使用SHOW CREATE查询。例如:

CREATE TABLE name UUID '28f1c61c-2970-457a-bffe-454156ddcfef' (n UInt64) ENGINE = ...;

可以使用一个原子查询:

EXCHANGE TABLES new_table AND old_table;

引擎参数

  • db_path — SQLite 数据库文件的路径.

数据类型的支持?

SQLiteClickHouse
INTEGER Int32
REAL Float32
TEXT String
BLOB String

技术细节和建议?

SQLite将整个数据库(定义、表、索引和数据本身)存储为主机上的单个跨平台文件。在写入过程中,SQLite会锁定整个数据库文件,因此写入操作是顺序执行的。读操作可以是多任务的。 SQLite不需要服务管理(如启动脚本)或基于GRANT和密码的访问控制。访问控制是通过授予数据库文件本身的文件系统权限来处理的。

使用示例?

数据库在ClickHouse,连接到SQLite:

CREATE DATABASE sqlite_db ENGINE = SQLite('sqlite.db');
SHOW TABLES FROM sqlite_db;

展示数据表中的内容:

SELECT * FROM sqlite_db.table1;

从ClickHouse表插入数据到SQLite表:

CREATE TABLE clickhouse_table(`col1` String,`col2` Int16) ENGINE = MergeTree() ORDER BY col2;
INSERT INTO clickhouse_table VALUES ('text',10);
INSERT INTO sqlite_db.table1 SELECT * FROM clickhouse_table;
SELECT * FROM sqlite_db.table1;

引擎参数

  • host:port — PostgreSQL服务地址
  • database — 远程数据库名次
  • user — PostgreSQL用户名称
  • password — PostgreSQL用户密码
  • schema - PostgreSQL 模式
  • use_table_cache — 定义数据库表结构是否已缓存或不进行。可选的。默认值: 0.

支持的数据类型?

PostgerSQLClickHouse
DATE Date
TIMESTAMP DateTime
REAL Float32
DOUBLE Float64
DECIMAL, NUMERIC Decimal
SMALLINT Int16
INTEGER Int32
BIGINT Int64
SERIAL UInt32
BIGSERIAL UInt64
TEXT, CHAR String
INTEGER Nullable(Int32)
ARRAY Array

使用示例?

ClickHouse中的数据库,与PostgreSQL服务器交换数据:

CREATE DATABASE test_database 
ENGINE = PostgreSQL('postgres1:5432', 'test_database', 'postgres', 'mysecretpassword', 1);
┌─name──────────┐
│ default │
│ test_database │
│ system │
└───────────────┘
┌─name───────┐
│ test_table │
└────────────┘
┌─id─┬─value─┐
│ 1 │ 2 │
└────┴───────┘
┌─int_id─┬─value─┐
│ 1 │ 2 │
│ 3 │ 4 │
└────────┴───────┘

当创建数据库时,参数use_table_cache被设置为1,ClickHouse中的表结构被缓存,因此没有被修改:

DESCRIBE TABLE test_database.test_table;

分离表并再次附加它之后,结构被更新了:

DETACH TABLE test_database.test_table;
ATTACH TABLE test_database.test_table;
DESCRIBE TABLE test_database.test_table;

引擎参数

  • zoo_path — ZooKeeper地址,同一个ZooKeeper路径对应同一个数据库。
  • shard_name — 分片的名字。数据库副本按shard_name分组到分片中。
  • replica_name — 副本的名字。同一分片的所有副本的副本名称必须不同。

!!! note "警告" 对于ReplicatedMergeTree表,如果没有提供参数,则使用默认参数:/clickhouse/tables/{uuid}/{shard}{replica}。这些可以在服务器设置default_replica_path和default_replica_name中更改。宏{uuid}被展开到表的uuid, {shard}{replica}被展开到服务器配置的值,而不是数据库引擎参数。但是在将来,可以使用Replicated数据库的shard_namereplica_name

使用方式?

使用Replicated数据库的DDL查询的工作方式类似于ON CLUSTER查询,但有细微差异。

首先,DDL请求尝试在启动器(最初从用户接收请求的主机)上执行。如果请求没有完成,那么用户立即收到一个错误,其他主机不会尝试完成它。如果在启动器上成功地完成了请求,那么所有其他主机将自动重试,直到完成请求。启动器将尝试在其他主机上等待查询完成(不超过distributed_ddl_task_timeout),并返回一个包含每个主机上查询执行状态的表。

错误情况下的行为是由distributed_ddl_output_mode设置调节的,对于Replicated数据库,最好将其设置为null_status_on_timeout - 例如,如果一些主机没有时间执行distributed_ddl_task_timeout的请求,那么不要抛出异常,但在表中显示它们的NULL状态。

system.clusters系统表包含一个名为复制数据库的集群,它包含数据库的所有副本。当创建/删除副本时,这个集群会自动更新,它可以用于Distributed表。

当创建数据库的新副本时,该副本会自己创建表。如果副本已经不可用很长一段时间,并且已经滞后于复制日志-它用ZooKeeper中的当前元数据检查它的本地元数据,将带有数据的额外表移动到一个单独的非复制数据库(以免意外地删除任何多余的东西),创建缺失的表,如果表名已经被重命名,则更新表名。数据在ReplicatedMergeTree级别被复制,也就是说,如果表没有被复制,数据将不会被复制(数据库只负责元数据)。

允许ALTER TABLE ATTACH|FETCH|DROP|DROP DETACHED|DETACH PARTITION|PART查询,但不允许复制。数据库引擎将只向当前副本添加/获取/删除分区/部件。但是,如果表本身使用了Replicated表引擎,那么数据将在使用ATTACH后被复制。

使用示例?

创建三台主机的集群:

node1 :) CREATE DATABASE r ENGINE=Replicated('some/path/r','shard1','replica1');
node2 :) CREATE DATABASE r ENGINE=Replicated('some/path/r','shard1','other_replica');
node3 :) CREATE DATABASE r ENGINE=Replicated('some/path/r','other_shard','{replica}');
┌─────hosts────────────┬──status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐ 
│ shard1|replica1 │ 0 │ │ 2 │ 0 │
│ shard1|other_replica │ 0 │ │ 1 │ 0 │
│ other_shard|r1 │ 0 │ │ 0 │ 0 │
└──────────────────────┴─────────┴───────┴─────────────────────┴──────────────────┘
┌─cluster─┬─shard_num─┬─replica_num─┬─host_name─┬─host_address─┬─port─┬─is_local─┐ 
│ r │ 1 │ 1 │ node3 │ 127.0.0.1 │ 9002 │ 0 │
│ r │ 2 │ 1 │ node2 │ 127.0.0.1 │ 9001 │ 0 │
│ r │ 2 │ 2 │ node1 │ 127.0.0.1 │ 9000 │ 1 │
└─────────┴───────────┴─────────────┴───────────┴──────────────┴──────┴──────────┘
┌─hosts─┬─groupArray(n)─┐ 
│ node1 │ [1,3,5,7,9] │
│ node2 │ [0,2,4,6,8] │
└───────┴───────────────┘

集群配置如下所示:

┌─cluster─┬─shard_num─┬─replica_num─┬─host_name─┬─host_address─┬─port─┬─is_local─┐ 
│ r │ 1 │ 1 │ node3 │ 127.0.0.1 │ 9002 │ 0 │
│ r │ 1 │ 2 │ node4 │ 127.0.0.1 │ 9003 │ 0 │
│ r │ 2 │ 1 │ node2 │ 127.0.0.1 │ 9001 │ 0 │
│ r │ 2 │ 2 │ node1 │ 127.0.0.1 │ 9000 │ 1 │
└─────────┴───────────┴─────────────┴───────────┴──────────────┴──────┴──────────┘
┌─hosts─┬─groupArray(n)─┐ 
│ node2 │ [1,3,5,7,9] │
│ node4 │ [0,2,4,6,8] │
└───────┴───────────────┘

表引擎

表引擎(即表的类型)决定了:

  • 数据的存储方式和位置,写到哪里以及从哪里读取数据
  • 支持哪些查询以及如何支持。
  • 并发数据访问。
  • 索引的使用(如果存在)。
  • 是否可以执行多线程请求。
  • 数据复制参数。

引擎类型

MergeTree?

适用于高负载任务的最通用和功能最强大的表引擎。这些引擎的共同特点是可以快速插入数据并进行后续的后台数据处理。 MergeTree系列引擎支持数据复制(使用Replicated* 的引擎版本),分区和一些其他引擎不支持的其他功能。

该类型的引擎:

  • MergeTree
  • ReplacingMergeTree
  • SummingMergeTree
  • AggregatingMergeTree
  • CollapsingMergeTree
  • VersionedCollapsingMergeTree
  • GraphiteMergeTree

日志?

具有最小功能的轻量级引擎。当您需要快速写入许多小表(最多约100万行)并在以后整体读取它们时,该类型的引擎是最有效的。

该类型的引擎:

  • TinyLog
  • StripeLog
  • Log

集成引擎?

用于与其他的数据存储与处理系统集成的引擎。 该类型的引擎:

  • Kafka
  • MySQL
  • ODBC
  • JDBC
  • HDFS

用于其他特定功能的引擎?

该类型的引擎:

  • Distributed
  • MaterializedView
  • Dictionary
  • Merge
  • File
  • Null
  • Set
  • Join
  • URL
  • View
  • Memory
  • Buffer

虚拟列

虚拟列是表引擎组成的一部分,它在对应的表引擎的源代码中定义。

您不能在 CREATE TABLE 中指定虚拟列,并且虚拟列不会包含在 SHOW CREATE TABLE 和 DESCRIBE TABLE 的查询结果中。虚拟列是只读的,所以您不能向虚拟列中写入数据。

如果想要查询虚拟列中的数据,您必须在SELECT查询中包含虚拟列的名字。SELECT * 不会返回虚拟列的内容。

若您创建的表中有一列与虚拟列的名字相同,那么虚拟列将不能再被访问。我们不建议您这样做。为了避免这种列名的冲突,虚拟列的名字一般都以下划线开头。

MergeTree

Clickhouse 中最强大的表引擎当属 MergeTree (合并树)引擎及该系列(*MergeTree)中的其他引擎。

MergeTree 系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以以数据片段的形式一个接着一个的快速写入,数据片段在后台按照一定的规则进行合并。相比在插入时不断修改(重写)已存储的数据,这种策略会高效很多。

主要特点:

  • 存储的数据按主键排序。

    这使得您能够创建一个小型的稀疏索引来加快数据检索。

  • 如果指定了 分区键 的话,可以使用分区。

    在相同数据集和相同结果集的情况下 ClickHouse 中某些带分区的操作会比普通操作更快。查询中指定了分区键时 ClickHouse 会自动截取分区数据。这也有效增加了查询性能。

  • 支持数据副本。

    ReplicatedMergeTree 系列的表提供了数据副本功能。更多信息,请参阅 数据副本 一节。

  • 支持数据采样。

    需要的话,您可以给表设置一个采样方法。

!!! note "注意" 合并 引擎并不属于 *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, ...]
  • merge_with_ttl_timeout — TTL合并频率的最小间隔时间,单位:秒。默认值: 86400 (1 天)。
  • write_final_mark — 是否启用在数据片段尾部写入最终索引标记。默认值: 1(不要关闭)。
  • merge_max_block_size — 在块中进行合并操作时的最大行数限制。默认值:8192
  • storage_policy — 存储策略。 参见 使用具有多个块的设备进行数据存储.
  • min_bytes_for_wide_part,min_rows_for_wide_part 在数据片段中可以使用Wide格式进行存储的最小字节数/行数。您可以不设置、只设置一个,或全都设置。参考:数据存储
  • max_parts_in_total - 所有分区中最大块的数量(意义不明)
  • max_compress_block_size - 在数据压缩写入表前,未压缩数据块的最大大小。您可以在全局设置中设置该值(参见max_compress_block_size)。建表时指定该值会覆盖全局设置。
  • min_compress_block_size - 在数据压缩写入表前,未压缩数据块的最小大小。您可以在全局设置中设置该值(参见min_compress_block_size)。建表时指定该值会覆盖全局设置。
  • max_partitions_to_read - 一次查询中可访问的分区最大数。您可以在全局设置中设置该值(参见max_partitions_to_read)。
  • 示例配置

    ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity=8192

    如果指定查询如下:

    • CounterID in ('a', 'h'),服务器会读取标记号在 [0, 3) 和 [6, 8) 区间中的数据。
    • CounterID IN ('a', 'h') AND Date = 3,服务器会读取标记号在 [1, 3) 和 [7, 8) 区间中的数据。
    • Date = 3,服务器会读取标记号在 [1, 10] 区间中的数据。

    上面例子可以看出使用索引通常会比全表描述要高效。

    稀疏索引会引起额外的数据读取。当读取主键单个区间范围的数据时,每个数据块中最多会多读 index_granularity * 2 行额外的数据。

    稀疏索引使得您可以处理极大量的行,因为大多数情况下,这些索引常驻于内存。

    ClickHouse 不要求主键唯一,所以您可以插入多条具有相同主键的行。

    您可以在PRIMARY KEYORDER BY条件中使用可为空的类型的表达式,但强烈建议不要这么做。为了启用这项功能,请打开allow_nullable_key,NULLS_LAST规则也适用于ORDER BY条件中有NULL值的情况下。

    主键的选择?

    主键中列的数量并没有明确的限制。依据数据结构,您可以在主键包含多些或少些列。这样可以:

    • 改善索引的性能。

    • 如果当前主键是 (a, b) ,在下列情况下添加另一个 c 列会提升性能:

    • 查询会使用 c 列作为条件

    • 很长的数据范围( index_granularity 的数倍)里 (a, b) 都是相同的值,并且这样的情况很普遍。换言之,就是加入另一列后,可以让您的查询略过很长的数据范围。

    • 改善数据压缩。

      ClickHouse 以主键排序片段数据,所以,数据的一致性越高,压缩越好。

    • 在CollapsingMergeTree 和 SummingMergeTree 引擎里进行数据合并时会提供额外的处理逻辑。

      在这种情况下,指定与主键不同的 排序键 也是有意义的。

    长的主键会对插入性能和内存消耗有负面影响,但主键中额外的列并不影响 SELECT 查询的性能。

    可以使用 ORDER BY tuple() 语法创建没有主键的表。在这种情况下 ClickHouse 根据数据插入的顺序存储。如果在使用 INSERT ... SELECT 时希望保持数据的排序,请设置 max_insert_threads = 1。

    想要根据初始顺序进行数据查询,使用 单线程查询

    选择与排序键不同的主键?

    Clickhouse可以做到指定一个跟排序键不一样的主键,此时排序键用于在数据片段中进行排序,主键用于在索引文件中进行标记的写入。这种情况下,主键表达式元组必须是排序键表达式元组的前缀(即主键为(a,b),排序列必须为(a,b,**))。

    当使用 SummingMergeTree 和 AggregatingMergeTree 引擎时,这个特性非常有用。通常在使用这类引擎时,表里的列分两种:维度 和 度量 。典型的查询会通过任意的 GROUP BY 对度量列进行聚合并通过维度列进行过滤。由于 SummingMergeTree 和 AggregatingMergeTree 会对排序键相同的行进行聚合,所以把所有的维度放进排序键是很自然的做法。但这将导致排序键中包含大量的列,并且排序键会伴随着新添加的维度不断的更新。

    在这种情况下合理的做法是,只保留少量的列在主键当中用于提升扫描效率,将维度列添加到排序键中。

    对排序键进行 ALTER 是轻量级的操作,因为当一个新列同时被加入到表里和排序键里时,已存在的数据片段并不需要修改。由于旧的排序键是新排序键的前缀,并且新添加的列中没有数据,因此在表修改时的数据对于新旧的排序键来说都是有序的。

    索引和分区在查询中的应用?

    对于 SELECT 查询,ClickHouse 分析是否可以使用索引。如果 WHERE/PREWHERE 子句具有下面这些表达式(作为完整WHERE条件的一部分或全部)则可以使用索引:进行相等/不相等的比较;对主键列或分区列进行IN运算、有固定前缀的LIKE运算(如name like 'test%')、函数运算(部分函数适用),还有对上述表达式进行逻辑运算。

    因此,在索引键的一个或多个区间上快速地执行查询是可能的。下面例子中,指定标签;指定标签和日期范围;指定标签和日期;指定多个标签和日期范围等执行查询,都会非常快。

    当引擎配置如下时:

        ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate) SETTINGS index_granularity=8192

    ClickHouse 会依据主键索引剪掉不符合的数据,依据按月分区的分区键剪掉那些不包含符合数据的分区。

    上文的查询显示,即使索引用于复杂表达式,因为读表操作经过优化,所以使用索引不会比完整扫描慢。

    下面这个例子中,不会使用索引。

    SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'

    *MergeTree 系列的表可以指定跳数索引。 跳数索引是指数据片段按照粒度(建表时指定的index_granularity)分割成小块后,将上述SQL的granularity_value数量的小块组合成一个大的块,对这些大块写入索引信息,这样有助于使用where筛选时跳过大量不必要的数据,减少SELECT需要读取的数据量。

    示例

    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()
    ...

    可用的索引类型?

    • minmax 存储指定表达式的极值(如果表达式是 tuple ,则存储 tuple 中每个元素的极值),这些信息用于跳过数据块,类似主键。

    • set(max_rows) 存储指定表达式的不重复值(不超过 max_rows 个,max_rows=0 则表示『无限制』)。这些信息可用于检查数据块是否满足 WHERE 条件。

    • ngrambf_v1(n, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed) 存储一个包含数据块中所有 n元短语(ngram) 的 布隆过滤器 。只可用在字符串上。 可用于优化 equals , like 和 in 表达式的性能。

      • n – 短语长度。
      • size_of_bloom_filter_in_bytes – 布隆过滤器大小,字节为单位。(因为压缩得好,可以指定比较大的值,如 256 或 512)。
      • number_of_hash_functions – 布隆过滤器中使用的哈希函数的个数。
      • random_seed – 哈希函数的随机种子。
    • tokenbf_v1(size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed) 跟 ngrambf_v1 类似,但是存储的是token而不是ngrams。Token是由非字母数字的符号分割的序列。

    • bloom_filter(bloom_filter([false_positive]) – 为指定的列存储布隆过滤器

      可选参数false_positive用来指定从布隆过滤器收到错误响应的几率。取值范围是 (0,1),默认值:0.025

      支持的数据类型:Int*UInt*Float*EnumDateDateTimeStringFixedStringArrayLowCardinalityNullable

      以下函数会用到这个索引: equals, notEquals, in, notIn, has

    INDEX sample_index (u64 * length(s)) TYPE minmax GRANULARITY 4
    INDEX sample_index2 (u64 * length(str), i32 + f64 * 100, date, str) TYPE set(100) GRANULARITY 4
    INDEX sample_index3 (lower(str), str) TYPE ngrambf_v1(3, 256, 2, 0) GRANULARITY 4

    要定义interval, 需要使用 时间间隔 操作符。

    TTL date_time + INTERVAL 1 MONTH
    TTL date_time + INTERVAL 15 HOUR

    为表中已存在的列字段添加 TTL

    ALTER TABLE example_table
    MODIFY COLUMN
    c String TTL d + INTERVAL 1 DAY;

    表 TTL?

    表可以设置一个用于移除过期行的表达式,以及多个用于在磁盘或卷上自动转移数据片段的表达式。当表中的行过期时,ClickHouse 会删除所有对应的行。对于数据片段的转移特性,必须所有的行都满足转移条件。

    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

    ALTER TABLE example_table
    MODIFY TTL d + INTERVAL 1 DAY;

    创建一张表,设置过期的列会被聚合。列x包含每组行中的最大值,y为最小值,d为可能任意值。

    CREATE TABLE table_for_aggregation
    (
    d DateTime,
    k1 Int,
    k2 Int,
    x Int,
    y Int
    )
    ENGINE = MergeTree
    ORDER BY (k1, k2)
    TTL d + INTERVAL 1 MONTH GROUP BY k1, k2 SET x = max(x), y = min(y);

    标签:

    •  — 磁盘名,名称必须与其他磁盘不同.
    • path — 服务器将用来存储数据 (data 和 shadow 目录) 的路径, 应当以 ‘/’ 结尾.
    • keep_free_space_bytes — 需要保留的剩余磁盘空间.

    磁盘定义的顺序无关紧要。

    存储策略配置:

    <storage_configuration>
    ...
    <policies>
    <policy_name_1>
    <volumes>
    <volume_name_1>
    <disk>disk_name_from_disks_configurationdisk>
    <max_data_part_size_bytes>1073741824max_data_part_size_bytes>
    volume_name_1>
    <volume_name_2>

    volume_name_2>

    volumes>
    <move_factor>0.2move_factor>
    policy_name_1>
    <policy_name_2>

    policy_name_2>


    policies>
    ...
    storage_configuration>

    在给出的例子中, hdd_in_order 策略实现了 循环制 方法。因此这个策略只定义了一个卷(single),数据片段会以循环的顺序全部存储到它的磁盘上。当有多个类似的磁盘挂载到系统上,但没有配置 RAID 时,这种策略非常有用。请注意一个每个独立的磁盘驱动都并不可靠,您可能需要用3份或更多的复制份数来补偿它。

    如果在系统中有不同类型的磁盘可用,可以使用 moving_from_ssd_to_hddhot 卷由 SSD 磁盘(fast_ssd)组成,这个卷上可以存储的数据片段的最大大小为 1GB。所有大于 1GB 的数据片段都会被直接存储到 cold 卷上,cold 卷包含一个名为 disk1 的 HDD 磁盘。 同样,一旦 fast_ssd 被填充超过 80%,数据会通过后台进程向 disk1 进行转移。

    存储策略中卷的枚举顺序是很重要的。因为当一个卷被充满时,数据会向下一个卷转移。磁盘的枚举顺序同样重要,因为数据是依次存储在磁盘上的。

    在创建表时,可以应用存储策略:

    CREATE TABLE table_with_non_default_policy (
    EventDate Date,
    OrderID UInt64,
    BannerID UInt64,
    SearchPhrase String
    ) ENGINE = MergeTree
    ORDER BY (OrderID, BannerID)
    PARTITION BY toYYYYMM(EventDate)
    SETTINGS storage_policy = 'moving_from_ssd_to_hdd'

    必须的参数:

    • endpoint - S3的结点URL,以pathvirtual hosted格式书写。
    • access_key_id - S3的Access Key ID。
    • secret_access_key - S3的Secret Access Key。

    可选参数:

    • region - S3的区域名称
    • use_environment_credentials - 从环境变量AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY和AWS_SESSION_TOKEN中读取认证参数。默认值为false
    • use_insecure_imds_request - 如果设置为true,S3客户端在认证时会使用不安全的IMDS请求。默认值为false
    • proxy - 访问S3结点URL时代理设置。每一个uri项的值都应该是合法的代理URL。
    • connect_timeout_ms - Socket连接超时时间,默认值为10000,即10秒。
    • request_timeout_ms - 请求超时时间,默认值为5000,即5秒。
    • retry_attempts - 请求失败后的重试次数,默认值为10。
    • single_read_retries - 读过程中连接丢失后重试次数,默认值为4。
    • min_bytes_for_seek - 使用查找操作,而不是顺序读操作的最小字节数,默认值为1000。
    • metadata_path - 本地存放S3元数据文件的路径,默认值为/var/lib/clickhouse/disks//
    • cache_enabled - 是否允许缓存标记和索引文件。默认值为true
    • cache_path - 本地缓存标记和索引文件的路径。默认值为/var/lib/clickhouse/disks//cache/
    • skip_access_check - 如果为true,Clickhouse启动时不检查磁盘是否可用。默认为false
    • server_side_encryption_customer_key_base64 - 如果指定该项的值,请求时会加上为了访问SSE-C加密数据而必须的头信息。

    S3磁盘也可以设置冷热存储:

    <storage_configuration>
    ...
    <disks>
    <s3>
    <type>s3type>
    <endpoint>https://storage.yandexcloud.net/my-bucket/root-path/endpoint>
    <access_key_id>your_access_key_idaccess_key_id>
    <secret_access_key>your_secret_access_keysecret_access_key>
    s3>
    disks>
    <policies>
    <s3_main>
    <volumes>
    <main>
    <disk>s3disk>
    main>
    volumes>
    s3_main>
    <s3_cold>
    <volumes>
    <main>
    <disk>defaultdisk>
    main>
    <external>
    <disk>s3disk>
    external>
    volumes>
    <move_factor>0.2move_factor>
    s3_cold>
    policies>
    ...
    storage_configuration>

    有关查询参数的说明,请参阅 查询说明.

    引擎参数

    VersionedCollapsingMergeTree(sign, version)
    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
    ) ENGINE [=] VersionedCollapsingMergeTree(date-column [, samp#table_engines_versionedcollapsingmergetreeling_expression], (primary, key), index_granularity, sign, version)

    在稍后的某个时候,我们注册用户活动的变化,并用以下两行写入它。

    ┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
    │ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 |
    │ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 |
    └─────────────────────┴───────────┴──────────┴──────┴─────────┘

    可以删除,折叠对象的无效(旧)状态。 VersionedCollapsingMergeTree 在合并数据部分时执行此操作。

    要了解为什么每次更改都需要两行,请参阅 算法.

    使用注意事项

    1. 写入数据的程序应该记住对象的状态以取消它。 该 “cancel” 字符串应该是 “state” 与相反的字符串 Sign. 这增加了存储的初始大小,但允许快速写入数据。
    2. 列中长时间增长的数组由于写入负载而降低了引擎的效率。 数据越简单,效率就越高。
    3. SELECT 结果很大程度上取决于对象变化历史的一致性。 准备插入数据时要准确。 不一致的数据将导致不可预测的结果,例如会话深度等非负指标的负值。

    算法?

    当ClickHouse合并数据部分时,它会删除具有相同主键和版本但 Sign值不同的一对行. 行的顺序并不重要。

    当ClickHouse插入数据时,它会按主键对行进行排序。 如果 Version 列不在主键中,ClickHouse将其隐式添加到主键作为最后一个字段并使用它进行排序。

    选择数据?

    ClickHouse不保证具有相同主键的所有行都将位于相同的结果数据部分中,甚至位于相同的物理服务器上。 对于写入数据和随后合并数据部分都是如此。 此外,ClickHouse流程 SELECT 具有多个线程的查询,并且无法预测结果中的行顺序。 这意味着,如果有必要从VersionedCollapsingMergeTree 表中得到完全 “collapsed” 的数据,聚合是必需的。

    要完成折叠,请使用 GROUP BY 考虑符号的子句和聚合函数。 例如,要计算数量,请使用 sum(Sign) 而不是 count(). 要计算的东西的总和,使用 sum(Sign * x) 而不是 sum(x),并添加 HAVING sum(Sign) > 0.

    聚合 countsum 和 avg 可以这样计算。 聚合 uniq 如果对象至少具有一个非折叠状态,则可以计算。 聚合 min 和 max 无法计算是因为 VersionedCollapsingMergeTree 不保存折叠状态值的历史记录。

    如果您需要提取数据 “collapsing” 但是,如果没有聚合(例如,要检查是否存在其最新值与某些条件匹配的行),则可以使用 FINAL 修饰 FROM 条件这种方法效率低下,不应与大型表一起使用。

    使用示例?

    示例数据:

    ┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
    │ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 |
    │ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 |
    │ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 |
    └─────────────────────┴───────────┴──────────┴──────┴─────────┘

    插入数据:

    INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1, 1)

    我们用两个 INSERT 查询以创建两个不同的数据部分。 如果我们使用单个查询插入数据,ClickHouse将创建一个数据部分,并且永远不会执行任何合并。

    获取数据:

    SELECT * FROM UAct

    我们在这里看到了什么,折叠的部分在哪里? 我们使用两个创建了两个数据部分 INSERT 查询。 该 SELECT 查询是在两个线程中执行的,结果是行的随机顺序。 由于数据部分尚未合并,因此未发生折叠。 ClickHouse在我们无法预测的未知时间点合并数据部分。

    这就是为什么我们需要聚合:

    SELECT
    UserID,
    sum(PageViews * Sign) AS PageViews,
    sum(Duration * Sign) AS Duration,
    Version
    FROM UAct
    GROUP BY UserID, Version
    HAVING sum(Sign) > 0

    如果我们不需要聚合,并希望强制折叠,我们可以使用 FINAL 修饰符 FROM 条款

    SELECT * FROM UAct FINAL

    这是一个非常低效的方式来选择数据。 不要把它用于数据量大的表。

    GraphiteMergeTree

    该引擎用来对 Graphite数据进行瘦身及汇总。对于想使用CH来存储Graphite数据的开发者来说可能有用。

    如果不需要对Graphite数据做汇总,那么可以使用任意的CH表引擎;但若需要,那就采用 GraphiteMergeTree 引擎。它能减少存储空间,同时能提高Graphite数据的查询效率。

    该引擎继承自 MergeTree.

    创建表?

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (
    Path String,
    Time DateTime,
    Value <Numeric_type>,
    Version <Numeric_type>
    ...
    ) ENGINE = GraphiteMergeTree(config_section)
    [PARTITION BY expr]
    [ORDER BY expr]
    [SAMPLE BY expr]
    [SETTINGS name=value, ...]

    !!! 注意 "Attention" 模式必须严格按顺序配置:

      1. 不含`function` or `retention`的Patterns
    1. 同时含有`function` and `retention`的Patterns
    1. `default`的Patterns.

    语句参数的说明,请参阅 建表语句描述。

    子句

    创建 AggregatingMergeTree 表时,需用跟创建 MergeTree 表一样的子句。

    已弃用的建表方法

    SELECT 和 INSERT?

    要插入数据,需使用带有 -State- 聚合函数的 INSERT SELECT 语句。 从 AggregatingMergeTree 表中查询数据时,需使用 GROUP BY 子句并且要使用与插入时相同的聚合函数,但后缀要改为 -Merge 。

    对于 SELECT 查询的结果, AggregateFunction 类型的值对 ClickHouse 的所有输出格式都实现了特定的二进制表示法。在进行数据转储时,例如使用 TabSeparated 格式进行 SELECT 查询,那么这些转储数据也能直接用 INSERT 语句导回。

    聚合物化视图的示例?

    创建一个跟踪 test.visits 表的 AggregatingMergeTree 物化视图:

    CREATE MATERIALIZED VIEW test.basic
    ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(StartDate) ORDER BY (CounterID, StartDate)
    AS SELECT
    CounterID,
    StartDate,
    sumState(Sign) AS Visits,
    uniqState(UserID) AS Users
    FROM test.visits
    GROUP BY CounterID, StartDate;

    数据会同时插入到表和视图中,并且视图 test.basic 会将里面的数据聚合。

    要获取聚合数据,我们需要在 test.basic 视图上执行类似 SELECT ... GROUP BY ... 这样的查询 :

    SELECT
    StartDate,
    sumMerge(Visits) AS Visits,
    uniqMerge(Users) AS Users
    FROM test.basic
    GROUP BY StartDate
    ORDER BY StartDate;

    CollapsingMergeTree

    该引擎继承于 MergeTree,并在数据块合并算法中添加了折叠行的逻辑。

    CollapsingMergeTree 会异步的删除(折叠)这些除了特定列 Sign 有 1 和 -1 的值以外,其余所有字段的值都相等的成对的行。没有成对的行会被保留。更多的细节请看本文的折叠部分。

    因此,该引擎可以显著的降低存储量并提高 SELECT 查询效率。

    建表?

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
    ) ENGINE = CollapsingMergeTree(sign)
    [PARTITION BY expr]
    [ORDER BY expr]
    [SAMPLE BY expr]
    [SETTINGS name=value, ...]

    一段时间后,我们写入下面的两行来记录用户活动的变化。

    ┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
    │ 4324182021466249494 │ 5 │ 146 │ -1 │
    │ 4324182021466249494 │ 6 │ 185 │ 1 │
    └─────────────────────┴───────────┴──────────┴──────┘

    可以在折叠对象的失效(老的)状态的时候被删除。CollapsingMergeTree 会在合并数据片段的时候做这件事。

    为什么我们每次改变需要 2 行可以阅读算法段。

    这种方法的特殊属性

    1. 写入的程序应该记住对象的状态从而可以取消它。?取消?字符串应该是?状态?字符串的复制,除了相反的 Sign。它增加了存储的初始数据的大小,但使得写入数据更快速。
    2. 由于写入的负载,列中长的增长阵列会降低引擎的效率。数据越简单,效率越高。
    3. SELECT 的结果很大程度取决于对象变更历史的一致性。在准备插入数据时要准确。在不一致的数据中会得到不可预料的结果,例如,像会话深度这种非负指标的负值。

    算法?

    当 ClickHouse 合并数据片段时,每组具有相同主键的连续行被减少到不超过两行,一行 Sign = 1(?状态?行),另一行 Sign = -1 (?取消?行),换句话说,数据项被折叠了。

    对每个结果的数据部分 ClickHouse 保存:

    1. 第一个?取消?和最后一个?状态?行,如果?状态?和?取消?行的数量匹配和最后一个行是?状态?行
    2. 最后一个?状态?行,如果?状态?行比?取消?行多一个或一个以上。
    3. 第一个?取消?行,如果?取消?行比?状态?行多一个或一个以上。
    4. 没有行,在其他所有情况下。

    合并会继续,但是 ClickHouse 会把此情况视为逻辑错误并将其记录在服务日志中。这个错误会在相同的数据被插入超过一次时出现。

    建表:

    CREATE TABLE UAct
    (
    UserID UInt64,
    PageViews UInt8,
    Duration UInt8,
    Sign Int8
    )
    ENGINE = CollapsingMergeTree(Sign)
    ORDER BY UserID
    INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1),(4324182021466249494, 6, 185, 1)

    我们看到了什么,哪里有折叠?

    通过两个 INSERT 请求,我们创建了两个数据片段。SELECT 请求在两个线程中被执行,我们得到了随机顺序的行。没有发生折叠是因为还没有合并数据片段。ClickHouse 在一个我们无法预料的未知时刻合并数据片段。

    因此我们需要聚合:

    SELECT
    UserID,
    sum(PageViews * Sign) AS PageViews,
    sum(Duration * Sign) AS Duration
    FROM UAct
    GROUP BY UserID
    HAVING sum(Sign) > 0

    如果我们不需要聚合并想要强制进行折叠,我们可以在 FROM 从句中使用 FINAL 修饰语。

    SELECT * FROM UAct FINAL

    这种查询数据的方法是非常低效的。不要在大表中使用它。

    ReplacingMergeTree

    该引擎和 MergeTree 的不同之处在于它会删除排序键值相同的重复项。

    数据的去重只会在数据合并期间进行。合并会在后台一个不确定的时间进行,因此你无法预先作出计划。有一些数据可能仍未被处理。尽管你可以调用 OPTIMIZE 语句发起计划外的合并,但请不要依靠它,因为 OPTIMIZE 语句会引发对数据的大量读写。

    因此,ReplacingMergeTree 适用于在后台清除重复的数据以节省空间,但是它不保证没有重复的数据出现。

    建表?

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
    ) ENGINE = ReplacingMergeTree([ver])
    [PARTITION BY expr]
    [ORDER BY expr]
    [SAMPLE BY expr]
    [SETTINGS name=value, ...]

    需要 ZooKeeper 3.4.5 或更高版本。

    你可以配置任何现有的 ZooKeeper 集群,系统会使用里面的目录来存取元数据(该目录在创建可复制表时指定)。

    如果配置文件中没有设置 ZooKeeper ,则无法创建复制表,并且任何现有的复制表都将变为只读。

    SELECT 查询并不需要借助 ZooKeeper ,副本并不影响 SELECT 的性能,查询复制表与非复制表速度是一样的。查询分布式表时,ClickHouse的处理方式可通过设置 max_replica_delay_for_distributed_queries 和 fallback_to_stale_replicas_for_distributed_queries 修改。

    对于每个 INSERT 语句,会通过几个事务将十来个记录添加到 ZooKeeper。(确切地说,这是针对每个插入的数据块; 每个 INSERT 语句的每 max_insert_block_size = 1048576 行和最后剩余的都各算作一个块。)相比非复制表,写 zk 会导致 INSERT 的延迟略长一些。但只要你按照建议每秒不超过一个 INSERT 地批量插入数据,不会有任何问题。一个 ZooKeeper 集群能给整个 ClickHouse 集群支撑协调每秒几百个 INSERT。数据插入的吞吐量(每秒的行数)可以跟不用复制的数据一样高。

    对于非常大的集群,你可以把不同的 ZooKeeper 集群用于不同的分片。然而,即使 Yandex.Metrica 集群(大约300台服务器)也证明还不需要这么做。

    复制是多主异步。 INSERT 语句(以及 ALTER )可以发给任意可用的服务器。数据会先插入到执行该语句的服务器上,然后被复制到其他服务器。由于它是异步的,在其他副本上最近插入的数据会有一些延迟。如果部分副本不可用,则数据在其可用时再写入。副本可用的情况下,则延迟时长是通过网络传输压缩数据块所需的时间。

    默认情况下,INSERT 语句仅等待一个副本写入成功后返回。如果数据只成功写入一个副本后该副本所在的服务器不再存在,则存储的数据会丢失。要启用数据写入多个副本才确认返回,使用 insert_quorum 选项。

    单个数据块写入是原子的。 INSERT 的数据按每块最多 max_insert_block_size = 1048576 行进行分块,换句话说,如果 INSERT 插入的行少于 1048576,则该 INSERT 是原子的。

    数据块会去重。对于被多次写的相同数据块(大小相同且具有相同顺序的相同行的数据块),该块仅会写入一次。这样设计的原因是万一在网络故障时客户端应用程序不知道数据是否成功写入DB,此时可以简单地重复 INSERT 。把相同的数据发送给多个副本 INSERT 并不会有问题。因为这些 INSERT 是完全相同的(会被去重)。去重参数参看服务器设置 merge_tree 。(注意:Replicated*MergeTree 才会去重,不需要 zookeeper 的不带 MergeTree 不会去重)

    在复制期间,只有要插入的源数据通过网络传输。进一步的数据转换(合并)会在所有副本上以相同的方式进行处理执行。这样可以最大限度地减少网络使用,这意味着即使副本在不同的数据中心,数据同步也能工作良好。(能在不同数据中心中的同步数据是副本机制的主要目标。)

    你可以给数据做任意多的副本。Yandex.Metrica 在生产中使用双副本。某一些情况下,给每台服务器都使用 RAID-5 或 RAID-6 和 RAID-10。是一种相对可靠和方便的解决方案。

    系统会监视副本数据同步情况,并能在发生故障后恢复。故障转移是自动的(对于小的数据差异)或半自动的(当数据差异很大时,这可能意味是有配置错误)。

    创建复制表?

    在表引擎名称上加上 Replicated 前缀。例如:ReplicatedMergeTree

    Replicated*MergeTree 参数

    • zoo_path — ZooKeeper 中该表的路径。
    • replica_name — ZooKeeper 中的该表的副本名称。

    示例:

    CREATE TABLE table_name
    (
    EventDate DateTime,
    CounterID UInt32,
    UserID UInt32
    ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}')
    PARTITION BY toYYYYMM(EventDate)
    ORDER BY (CounterID, EventDate, intHash32(UserID))
    SAMPLE BY intHash32(UserID)

    如上例所示,这些参数可以包含宏替换的占位符,即大括号的部分。它们会被替换为配置文件里 ‘macros’ 那部分配置的值。示例:

    <macros>
    <layer>05layer>
    <shard>02shard>
    <replica>example05-02-1.yandex.rureplica>
    macros>

    然后重启服务器。启动时,服务器会删除这些标志并开始恢复。

    在数据完全丢失后的恢复?

    如果其中一个服务器的所有数据和元数据都消失了,请按照以下步骤进行恢复:

    1. 在服务器上安装 ClickHouse。在包含分片标识符和副本的配置文件中正确定义宏配置,如果有用到的话,
    2. 如果服务器上有非复制表则必须手动复制,可以从副本服务器上(在 /var/lib/clickhouse/data/db_name/table_name/ 目录中)复制它们的数据。
    3. 从副本服务器上中复制位于 /var/lib/clickhouse/metadata/ 中的表定义信息。如果在表定义信息中显式指定了分片或副本标识符,请更正它以使其对应于该副本。(另外,启动服务器,然后会在 /var/lib/clickhouse/metadata/ 中的.sql文件中生成所有的 ATTACH TABLE 语句。) 4.要开始恢复,ZooKeeper 中创建节点 /path_to_table/replica_name/flags/force_restore_data,节点内容不限,或运行命令来恢复所有复制的表:sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data

    然后启动服务器(如果它已运行则重启)。数据会从副本中下载。

    另一种恢复方式是从 ZooKeeper(/path_to_table/replica_name)中删除有数据丢的副本的所有元信息,然后再按照?创建可复制表?中的描述重新创建副本。

    恢复期间的网络带宽没有限制。特别注意这一点,尤其是要一次恢复很多副本。

    MergeTree 转换为 ReplicatedMergeTree?

    我们使用 MergeTree 来表示 MergeTree系列 中的所有表引擎,ReplicatedMergeTree 同理。

    如果你有一个手动同步的 MergeTree 表,您可以将其转换为可复制表。如果你已经在 MergeTree 表中收集了大量数据,并且现在要启用复制,则可以执行这些操作。

    如果各个副本上的数据不一致,则首先对其进行同步,或者除保留的一个副本外,删除其他所有副本上的数据。

    重命名现有的 MergeTree 表,然后使用旧名称创建 ReplicatedMergeTree 表。 将数据从旧表移动到新表(/var/lib/clickhouse/data/db_name/table_name/)目录内的 ‘detached’ 目录中。 然后在其中一个副本上运行ALTER TABLE ATTACH PARTITION,将这些数据片段添加到工作集中。

    ReplicatedMergeTree 转换为 MergeTree?

    使用其他名称创建 MergeTree 表。将具有ReplicatedMergeTree表数据的目录中的所有数据移动到新表的数据目录中。然后删除ReplicatedMergeTree表并重新启动服务器。

    如果你想在不启动服务器的情况下清除 ReplicatedMergeTree 表:

    • 删除元数据目录中的相应 .sql 文件(/var/lib/clickhouse/metadata/)。
    • 删除 ZooKeeper 中的相应路径(/path_to_table/replica_name)。

    之后,你可以启动服务器,创建一个 MergeTree 表,将数据移动到其目录,然后重新启动服务器。

    当 ZooKeeper 集群中的元数据丢失或损坏时恢复方法?

    如果 ZooKeeper 中的数据丢失或损坏,如上所述,你可以通过将数据转移到非复制表来保存数据。

    SummingMergeTree

    该引擎继承自 MergeTree。区别在于,当合并 SummingMergeTree 表的数据片段时,ClickHouse 会把所有具有相同主键的行合并为一行,该行包含了被合并的行中具有数值数据类型的列的汇总值。如果主键的组合方式使得单个键值对应于大量的行,则可以显著的减少存储空间并加快数据查询的速度。

    我们推荐将该引擎和 MergeTree 一起使用。例如,在准备做报告的时候,将完整的数据存储在 MergeTree 表中,并且使用 SummingMergeTree 来存储聚合数据。这种方法可以使你避免因为使用不正确的主键组合方式而丢失有价值的数据。

    建表?

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
    ) ENGINE = SummingMergeTree([columns])
    [PARTITION BY expr]
    [ORDER BY expr]
    [SAMPLE BY expr]
    [SETTINGS name=value, ...]

    子句

    创建 SummingMergeTree 表时,需要与创建 MergeTree 表时相同的子句。

    已弃用的建表方法

    用法示例?

    考虑如下的表:

    CREATE TABLE summtt
    (
    key UInt32,
    value UInt32
    )
    ENGINE = SummingMergeTree()
    ORDER BY key

    ClickHouse可能不会完整的汇总所有行(见下文),因此我们在查询中使用了聚合函数 sum 和 GROUP BY 子句。

    SELECT key, sum(value) FROM summtt GROUP BY key

    数据处理?

    当数据被插入到表中时,他们将被原样保存。ClickHouse 定期合并插入的数据片段,并在这个时候对所有具有相同主键的行中的列进行汇总,将这些行替换为包含汇总数据的一行记录。

    ClickHouse 会按片段合并数据,以至于不同的数据片段中会包含具有相同主键的行,即单个汇总片段将会是不完整的。因此,聚合函数 sum() 和 GROUP BY 子句应该在(SELECT)查询语句中被使用,如上文中的例子所述。

    汇总的通用规则?

    列中数值类型的值会被汇总。这些列的集合在参数 columns 中被定义。

    如果用于汇总的所有列中的值均为0,则该行会被删除。

    如果列不在主键中且无法被汇总,则会在现有的值中任选一个。

    主键所在的列中的值不会被汇总。

    AggregateFunction 列中的汇总?

    对于 AggregateFunction 类型的列,ClickHouse 根据对应函数表现为 AggregatingMergeTree 引擎的聚合。

    嵌套结构?

    表中可以具有以特殊方式处理的嵌套数据结构。

    如果嵌套表的名称以 Map 结尾,并且包含至少两个符合以下条件的列:

    • 第一列是数值类型 (*Int*, Date, DateTime),我们称之为 key,
    • 其他的列是可计算的 (*Int*, Float32/64),我们称之为 (values...),

    然后这个嵌套表会被解释为一个 key => (values...) 的映射,当合并它们的行时,两个数据集中的元素会被根据 key 合并为相应的 (values...) 的汇总值。

    示例:

    [(1, 100)] + [(2, 150)] -> [(1, 100), (2, 150)]
    [(1, 100)] + [(1, 150)] -> [(1, 250)]
    [(1, 100)] + [(1, 150), (2, 150)] -> [(1, 250), (2, 150)]
    [(1, 100), (2, 150)] + [(1, -100)] -> [(2, 150)]
  • 非原子地写入数据。

    如果某些事情破坏了写操作,例如服务器的异常关闭,你将会得到一张包含了损坏数据的表。
  • 并行读取数据。

    在读取数据时,ClickHouse 使用多线程。 每个线程处理不同的数据块。

    查看建表请求的详细说明。

    写数据?

    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 请求从而在 data.bin 文件中创建两个数据块。

    ClickHouse 在查询数据时使用多线程。每个线程读取单独的数据块并在完成后独立的返回结果行。这样的结果是,大多数情况下,输出中块的顺序和输入时相应块的顺序是不同的。例如:

    SELECT * FROM stripe_log_table

    对结果排序(默认增序):

    SELECT * FROM stripe_log_table ORDER BY timestamp

    查看CREATE TABLE查询的详细描述。

    表的结构可以与原来的Hive表结构有所不同:

    • 列名应该与原来的Hive表相同,但你可以使用这些列中的一些,并以任何顺序,你也可以使用一些从其他列计算的别名列。
    • 列类型与原Hive表的列类型保持一致。
    • “Partition by expression”应与原Hive表保持一致,“Partition by expression”中的列应在表结构中。

    引擎参数

    • thrift://host:port — Hive Metastore 地址

    • database — 远程数据库名.

    • table — 远程数据表名.

    使用示例?

    如何使用HDFS文件系统的本地缓存?

    我们强烈建议您为远程文件系统启用本地缓存。基准测试显示,如果使用缓存,它的速度会快两倍。

    在使用缓存之前,请将其添加到 config.xml

    <local_cache_for_remote_fs>
    <enable>trueenable>
    <root_dir>local_cacheroot_dir>
    <limit_size>559096952limit_size>
    <bytes_read_before_flush>1048576bytes_read_before_flush>
    local_cache_for_remote_fs>

    在 ClickHouse 中建表?

    ClickHouse中的表,从上面创建的Hive表中获取数据:

    CREATE TABLE test.test_orc
    (
    `f_tinyint` Int8,
    `f_smallint` Int16,
    `f_int` Int32,
    `f_integer` Int32,
    `f_bigint` Int64,
    `f_float` Float32,
    `f_double` Float64,
    `f_decimal` Float64,
    `f_timestamp` DateTime,
    `f_date` Date,
    `f_string` String,
    `f_varchar` String,
    `f_bool` Bool,
    `f_binary` String,
    `f_array_int` Array(Int32),
    `f_array_string` Array(String),
    `f_array_float` Array(Float32),
    `f_array_array_int` Array(Array(Int32)),
    `f_array_array_string` Array(Array(String)),
    `f_array_array_float` Array(Array(Float32)),
    `day` String
    )
    ENGINE = Hive('thrift://localhost:9083', 'test', 'test_orc')
    PARTITION BY day

    SELECT *
    FROM test.test_orc
    SETTINGS input_format_orc_allow_missing_columns = 1

    Query id: c3eaffdc-78ab-43cd-96a4-4acc5b480658

    Row 1:
    ──────
    f_tinyint: 1
    f_smallint: 2
    f_int: 3
    f_integer: 4
    f_bigint: 5
    f_float: 6.11
    f_double: 7.22
    f_decimal: 8
    f_timestamp: 2021-12-04 04:00:44
    f_date: 2021-12-03
    f_string: hello world
    f_varchar: hello world
    f_bool: true
    f_binary: hello world
    f_array_int: [1,2,3]
    f_array_string: ['hello world','hello world']
    f_array_float: [1.1,1.2]
    f_array_array_int: [[1,2],[3,4]]
    f_array_array_string: [['a','b'],['c','d']]
    f_array_array_float: [[1.11,2.22],[3.33,4.44]]
    day: 2021-09-18


    1 rows in set. Elapsed: 0.078 sec.

    在 ClickHouse 中建表?

    ClickHouse 中的表, 从上面创建的Hive表中获取数据:

    CREATE TABLE test.test_parquet
    (
    `f_tinyint` Int8,
    `f_smallint` Int16,
    `f_int` Int32,
    `f_integer` Int32,
    `f_bigint` Int64,
    `f_float` Float32,
    `f_double` Float64,
    `f_decimal` Float64,
    `f_timestamp` DateTime,
    `f_date` Date,
    `f_string` String,
    `f_varchar` String,
    `f_char` String,
    `f_bool` Bool,
    `f_binary` String,
    `f_array_int` Array(Int32),
    `f_array_string` Array(String),
    `f_array_float` Array(Float32),
    `f_array_array_int` Array(Array(Int32)),
    `f_array_array_string` Array(Array(String)),
    `f_array_array_float` Array(Array(Float32)),
    `day` String
    )
    ENGINE = Hive('thrift://localhost:9083', 'test', 'test_parquet')
    PARTITION BY day
    SELECT *
    FROM test_parquet
    SETTINGS input_format_parquet_allow_missing_columns = 1

    Query id: 4e35cf02-c7b2-430d-9b81-16f438e5fca9

    Row 1:
    ──────
    f_tinyint: 1
    f_smallint: 2
    f_int: 3
    f_integer: 4
    f_bigint: 5
    f_float: 6.11
    f_double: 7.22
    f_decimal: 8
    f_timestamp: 2021-12-14 17:54:56
    f_date: 2021-12-14
    f_string: hello world
    f_varchar: hello world
    f_char: hello world
    f_bool: true
    f_binary: hello world
    f_array_int: [1,2,3]
    f_array_string: ['hello world','hello world']
    f_array_float: [1.1,1.2]
    f_array_array_int: [[1,2],[3,4]]
    f_array_array_string: [['a','b'],['c','d']]
    f_array_array_float: [[1.11,2.22],[3.33,4.44]]
    day: 2021-09-18

    1 rows in set. Elapsed: 0.357 sec.

    在 ClickHouse 中建表?

    ClickHouse中的表, 从上面创建的Hive表中获取数据:

    CREATE TABLE test.test_text
    (
    `f_tinyint` Int8,
    `f_smallint` Int16,
    `f_int` Int32,
    `f_integer` Int32,
    `f_bigint` Int64,
    `f_float` Float32,
    `f_double` Float64,
    `f_decimal` Float64,
    `f_timestamp` DateTime,
    `f_date` Date,
    `f_string` String,
    `f_varchar` String,
    `f_char` String,
    `f_bool` Bool,
    `day` String
    )
    ENGINE = Hive('thrift://localhost:9083', 'test', 'test_text')
    PARTITION BY day
    SELECT *
    FROM test.test_text
    SETTINGS input_format_skip_unknown_fields = 1, input_format_with_names_use_header = 1, date_time_input_format = 'best_effort'

    Query id: 55b79d35-56de-45b9-8be6-57282fbf1f44

    Row 1:
    ──────
    f_tinyint: 1
    f_smallint: 2
    f_int: 3
    f_integer: 4
    f_bigint: 5
    f_float: 6.11
    f_double: 7.22
    f_decimal: 8
    f_timestamp: 2021-12-14 18:11:17
    f_date: 2021-12-14
    f_string: hello world
    f_varchar: hello world
    f_char: hello world
    f_bool: true
    day: 2021-09-18

    MongoDB

    MongoDB 引擎是只读表引擎,允许从远程 MongoDB 集合中读取数据(SELECT查询)。引擎只支持非嵌套的数据类型。不支持 INSERT 查询。

    创建一张表?

    CREATE TABLE [IF NOT EXISTS] [db.]table_name
    (
    name1 [type1],
    name2 [type2],
    ...
    ) ENGINE = MongoDB(host:port, database, collection, user, password);

    查询:

    SELECT COUNT() FROM mongo_table;

    引擎参数

    • db_path — SQLite数据库文件的具体路径地址。
    • table — SQLite数据库中的表名。

    使用示例?

    显示创建表的查询语句:

    SHOW CREATE TABLE sqlite_db.table2;

    从数据表查询数据:

    SELECT * FROM sqlite_db.table2 ORDER BY col1;

    详见

    • SQLite 引擎
    • sqlite 表方法函数

    EmbeddedRocksDB 引擎

    这个引擎允许 ClickHouse 与 rocksdb 进行集成。

    创建一张表?

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
    ) ENGINE = EmbeddedRocksDB PRIMARY KEY(primary_key_name)

    指标?

    还有一个system.rocksdb 表, 公开rocksdb的统计信息:

    SELECT
    name,
    value
    FROM system.rocksdb

    ┌─name──────────────────────┬─value─┐
    no.file.opens │ 1
    │ number.block.decompressed │ 1
    └───────────────────────────┴───────┘

    必要参数:

    • rabbitmq_host_port – 主机名:端口号 (比如, localhost:5672).
    • rabbitmq_exchange_name – RabbitMQ exchange 名称.
    • rabbitmq_format – 消息格式. 使用与SQLFORMAT函数相同的标记,如JSONEachRow。 更多信息,请参阅 Formats 部分.

    可选参数:

    • rabbitmq_exchange_type – RabbitMQ exchange 的类型: directfanouttopicheadersconsistent_hash. 默认是: fanout.
    • rabbitmq_routing_key_list – 一个以逗号分隔的路由键列表.
    • rabbitmq_row_delimiter – 用于消息结束的分隔符.
    • rabbitmq_schema – 如果格式需要模式定义,必须使用该参数。比如, Cap’n Proto 需要模式文件的路径以及根 schema.capnp:Message 对象的名称.
    • rabbitmq_num_consumers – 每个表的消费者数量。默认:1。如果一个消费者的吞吐量不够,可以指定更多的消费者.
    • rabbitmq_num_queues – 队列的总数。默认值: 1. 增加这个数字可以显著提高性能.
    • rabbitmq_queue_base - 指定一个队列名称的提示。这个设置的使用情况如下.
    • rabbitmq_deadletter_exchange - 为dead letter exchange指定名称。你可以用这个 exchange 的名称创建另一个表,并在消息被重新发布到 dead letter exchange 的情况下收集它们。默认情况下,没有指定 dead letter exchange。Specify name for a dead letter exchange.
    • rabbitmq_persistent - 如果设置为 1 (true), 在插入查询中交付模式将被设置为 2 (将消息标记为 'persistent'). 默认是: 0.
    • rabbitmq_skip_broken_messages – RabbitMQ 消息解析器对每块模式不兼容消息的容忍度。默认值:0. 如果 rabbitmq_skip_broken_messages = N,那么引擎将跳过 N 个无法解析的 RabbitMQ 消息(一条消息等于一行数据)。
    • rabbitmq_max_block_size
    • rabbitmq_flush_interval_ms

    同时,格式的设置也可以与 rabbitmq 相关的设置一起添加。

    示例:

      CREATE TABLE queue (
    key UInt64,
    value UInt64,
    date DateTime
    ) ENGINE = RabbitMQ SETTINGS rabbitmq_host_port = 'localhost:5672',
    rabbitmq_exchange_name = 'exchange1',
    rabbitmq_format = 'JSONEachRow',
    rabbitmq_num_consumers = 5,
    date_time_input_format = 'best_effort';

    可选配置:

     <rabbitmq>
    <vhost>clickhousevhost>
    rabbitmq>

    虚拟列?

    • _exchange_name - RabbitMQ exchange 名称.
    • _channel_id - 接收消息的消费者所声明的频道ID.
    • _delivery_tag - 收到消息的DeliveryTag. 以每个频道为范围.
    • _redelivered - 消息的redelivered标志.
    • _message_id - 收到的消息的ID;如果在消息发布时被设置,则为非空.
    • _timestamp - 收到的消息的时间戳;如果在消息发布时被设置,则为非空.

    PostgreSQL

    PostgreSQL 引擎允许 ClickHouse 对存储在远程 PostgreSQL 服务器上的数据执行 SELECT 和 INSERT 查询.

    创建一张表?

    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],
    ...
    ) ENGINE = PostgreSQL('host:port', 'database', 'table', 'user', 'password'[, `schema`]);

    用法示例?

    PostgreSQL 中的表:

    postgres=# CREATE TABLE "public"."test" (
    "int_id" SERIAL,
    "int_nullable" INT NULL DEFAULT NULL,
    "float" FLOAT NOT NULL,
    "str" VARCHAR(100) NOT NULL DEFAULT '',
    "float_nullable" FLOAT NULL DEFAULT NULL,
    PRIMARY KEY (int_id));

    CREATE TABLE

    postgres=# INSERT INTO test (int_id, str, "float") VALUES (1,'test',2);
    INSERT 0 1

    postgresql> SELECT * FROM test;
    int_id | int_nullable | float | str | float_nullable
    --------+--------------+-------+------+----------------
    1 | | 2 | test |
    (1 row)
    SELECT * FROM postgresql_table WHERE str IN ('test');

    使用非默认的模式:

    postgres=# CREATE SCHEMA "nice.schema";

    postgres=# CREATE TABLE "nice.schema"."nice.table" (a integer);

    postgres=# INSERT INTO "nice.schema"."nice.table" SELECT i FROM generate_series(0, 99) as t(i)

    引擎参数

    • datasource_uri — 外部DBMS的URI或名字.

      URI格式: jdbc:://:/?user=&password=. MySQL示例: jdbc:mysql://localhost:3306/?user=root&password=root.

    • external_database — 外部DBMS的数据库名.

    • external_table — external_database中的外部表名或类似select * from table1 where column1=1的查询语句.

    用法示例?

    通过mysql控制台客户端来创建表

    Creating a table in MySQL server by connecting directly with it’s console client:

    mysql> CREATE TABLE `test`.`test` (
    -> `int_id` INT NOT NULL AUTO_INCREMENT,
    -> `int_nullable` INT NULL DEFAULT NULL,
    -> `float` FLOAT NOT NULL,
    -> `float_nullable` FLOAT NULL DEFAULT NULL,
    -> PRIMARY KEY (`int_id`));
    Query OK, 0 rows affected (0,09 sec)

    mysql> insert into test (`int_id`, `float`) VALUES (1,2);
    Query OK, 1 row affected (0,00 sec)

    mysql> select * from test;
    +------+----------+-----+----------+
    | int_id | int_nullable | float | float_nullable |
    +------+----------+-----+----------+
    | 1 | NULL | 2 | NULL |
    +------+----------+-----+----------+
    1 row in set (0,00 sec)
    SELECT *
    FROM jdbc_table
    INSERT INTO jdbc_table(`int_id`, `float`)
    SELECT toInt32(number), toFloat32(number * 1.0)
    FROM system.numbers

    ODBC

    允许 ClickHouse 通过 ODBC 方式连接到外部数据库.

    为了安全地实现 ODBC 连接,ClickHouse 使用了一个独立程序 clickhouse-odbc-bridge. 如果ODBC驱动程序是直接从 clickhouse-server中加载的,那么驱动问题可能会导致ClickHouse服务崩溃。 当有需要时,ClickHouse会自动启动 clickhouse-odbc-bridge。 ODBC桥梁程序与clickhouse-server来自相同的安装包.

    该引擎支持 Nullable 数据类型。

    创建表?

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (
    name1 [type1],
    name2 [type2],
    ...
    )
    ENGINE = ODBC(connection_settings, external_database, external_table)
    mysql> CREATE USER 'clickhouse'@'localhost' IDENTIFIED BY 'clickhouse';
    mysql> GRANT ALL PRIVILEGES ON *.* TO 'clickhouse'@'clickhouse' WITH GRANT OPTION;

    您可以从安装的 unixodbc 中使用 isql 实用程序来检查连接情况。

    $ isql -v mysqlconn
    +---------------------------------------+
    | Connected! |
    | |
    ...

    ClickHouse中的表,从MySQL表中检索数据:

    CREATE TABLE odbc_t
    (
    `int_id` Int32,
    `float_nullable` Nullable(Float32)
    )
    ENGINE = ODBC('DSN=mysqlconn', 'test', 'test')
    ┌─int_id─┬─float_nullable─┐
    │ 1 │ ???? │
    └────────┴────────────────┘

    Kafka

    此引擎与 Apache Kafka 结合使用。

    Kafka 特性:

    • 发布或者订阅数据流。
    • 容错存储机制。
    • 处理流数据。

    普罗托船长 需要 schema 文件路径以及根对象 schema.capnp:Message 的名字。

  • kafka_num_consumers – 单个表的消费者数量。默认值是:1,如果一个消费者的吞吐量不足,则指定更多的消费者。消费者的总数不应该超过 topic 中分区的数量,因为每个分区只能分配一个消费者。
  • 示例:

      CREATE TABLE queue (
    timestamp UInt64,
    level String,
    message String
    ) ENGINE = Kafka('localhost:9092', 'topic', 'group1', 'JSONEachRow');

    SELECT * FROM queue LIMIT 5;

    CREATE TABLE queue2 (
    timestamp UInt64,
    level String,
    message String
    ) ENGINE = Kafka SETTINGS kafka_broker_list = 'localhost:9092',
    kafka_topic_list = 'topic',
    kafka_group_name = 'group1',
    kafka_format = 'JSONEachRow',
    kafka_num_consumers = 4;

    CREATE TABLE queue2 (
    timestamp UInt64,
    level String,
    message String
    ) ENGINE = Kafka('localhost:9092', 'topic', 'group1')
    SETTINGS kafka_format = 'JSONEachRow',
    kafka_num_consumers = 4;

    为了提高性能,接受的消息被分组为 max_insert_block_size 大小的块。如果未在 stream_flush_interval_ms 毫秒内形成块,则不关心块的完整性,都会将数据刷新到表中。

    停止接收主题数据或更改转换逻辑,请 detach 物化视图:

      DETACH TABLE consumer;
    ATTACH TABLE consumer;

    有关详细配置选项列表,请参阅 librdkafka配置参考。在 ClickHouse 配置中使用下划线 (_) ,并不是使用点 (.)。例如,check.crcs=true 将是 true

    Kerberos 支持?

    对于使用了kerberos的kafka, 将security_protocol 设置为sasl_plaintext就够了,如果kerberos的ticket是由操作系统获取和缓存的。 clickhouse也支持自己使用keyfile的方式来维护kerbros的凭证。配置sasl_kerberos_service_name、sasl_kerberos_keytab、sasl_kerberos_principal三个子元素就可以。

    示例:

      
    <kafka>
    <security_protocol>SASL_PLAINTEXTsecurity_protocol>
    <sasl_kerberos_keytab>/home/kafkauser/kafkauser.keytabsasl_kerberos_keytab>
    <sasl_kerberos_principal>kafkauser/kafkahost@EXAMPLE.COMsasl_kerberos_principal>
    kafka>

    调用参数

    • host:port — MySQL 服务器地址。
    • database — 数据库的名称。
    • table — 表名称。
    • user — 数据库用户。
    • password — 用户密码。
    • replace_query — 将 INSERT INTO 查询是否替换为 REPLACE INTO 的标志。如果 replace_query=1,则替换查询
    • 'on_duplicate_clause' — 将 ON DUPLICATE KEY UPDATE 'on_duplicate_clause' 表达式添加到 INSERT 查询语句中。例如:impression = VALUES(impression) + impression。如果需要指定 'on_duplicate_clause',则需要设置 replace_query=0。如果同时设置 replace_query = 1 和 'on_duplicate_clause',则会抛出异常。

    此时,简单的 WHERE 子句(例如 =, !=, >, >=, <, <=)是在 MySQL 服务器上执行。

    其余条件以及 LIMIT 采样约束语句仅在对MySQL的查询完成后才在ClickHouse中执行。

    MySQL 引擎不支持 可为空 数据类型,因此,当从MySQL表中读取数据时,NULL 将转换为指定列类型的默认值(通常为0或空字符串)。

    分布式引擎

    分布式引擎本身不存储数据, 但可以在多个服务器上进行分布式查询。 读是自动并行的。读取时,远程服务器表的索引(如果有的话)会被使用。

    创建数据表?

    CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
    (
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
    ) ENGINE = Distributed(cluster, database, table[, sharding_key[, policy_name]])
    [SETTINGS name=value, ...]

    分布式引擎参数

    • cluster - 服务为配置中的集群名

    • database - 远程数据库名

    • table - 远程数据表名

    • sharding_key - (可选) 分片key

    • policy_name - (可选) 规则名,它会被用作存储临时文件以便异步发送数据

    详见

    • insert_distributed_sync 设置

    • MergeTree 查看示例

      分布式设置

    • fsync_after_insert - 对异步插入到分布式的文件数据执行fsync。确保操作系统将所有插入的数据刷新到启动节点磁盘上的一个文件中。

    • fsync_directories - 对目录执行fsync。保证操作系统在分布式表上进行异步插入相关操作(插入后,发送数据到分片等)后刷新目录元数据.

    • bytes_to_throw_insert - 如果超过这个数量的压缩字节将等待异步INSERT,将抛出一个异常。0 - 不抛出。默认值0.

    • bytes_to_delay_insert - 如果超过这个数量的压缩字节将等待异步INSERT,查询将被延迟。0 - 不要延迟。默认值0.

    • max_delay_to_insert - 最大延迟多少秒插入数据到分布式表,如果有很多挂起字节异步发送。默认值60。

    • monitor_batch_inserts - 等同于 distributed_directory_monitor_batch_inserts

    • monitor_split_batch_on_failure - 等同于distributed_directory_monitor_split_batch_on_failure

    • monitor_sleep_time_ms - 等同于 distributed_directory_monitor_sleep_time_ms

    • monitor_max_sleep_time_ms - 等同于 distributed_directory_monitor_max_sleep_time_ms

    !!! note "备注"

    **稳定性设置** (`fsync_...`):

    - 只影响异步插入(例如:`insert_distributed_sync=false`), 当数据首先存储在启动节点磁盘上,然后再异步发送到shard。
    — 可能会显著降低`insert`的性能
    - 影响将存储在分布式表文件夹中的数据写入 **接受您插入的节点** 。如果你需要保证写入数据到底层的MergeTree表中,请参阅 `system.merge_tree_settings` 中的持久性设置(`...fsync...`)

    **插入限制设置** (`..._insert`) 请见:

    - [insert_distributed_sync](/docs/zh/operations/settings/settings#insert_distributed_sync) 设置
    - [prefer_localhost_replica](/docs/zh/operations/settings/settings#settings-prefer-localhost-replica) 设置
    - `bytes_to_throw_insert` 在 `bytes_to_delay_insert` 之前处理,所以你不应该设置它的值小于 `bytes_to_delay_insert`

    数据将从logs集群中的所有服务器中,从位于集群中的每个服务器上的default.hits表读取。。 数据不仅在远程服务器上读取,而且在远程服务器上进行部分处理(在可能的范围内)。 例如,对于带有 GROUP BY的查询,数据将在远程服务器上聚合,聚合函数的中间状态将被发送到请求者服务器。然后将进一步聚合数据。

    您可以使用一个返回字符串的常量表达式来代替数据库名称。例如: currentDatabase()

     JOIN id_val_join USING (id) SETTINGS join_use_nulls = 1

    作为一种替换方式,可以从 Join表获取数据,需要设置好join的key字段值。

    SELECT joinGet('id_val_join', 'val', toUInt32(1))

    数据查询及插入?

    可以使用 INSERT语句向 Join引擎表中添加数据。如果表是通过指定 ANY限制参数来创建的,那么重复key的数据会被忽略。指定 ALL限制参数时,所有行记录都会被添加进去。

    不能通过 SELECT 语句直接从表中获取数据。请使用下面的方式:

    • 将表放在 JOIN 的右边进行查询
    • 调用 joinGet函数,就像从字典中获取数据一样来查询表。

    使用限制及参数设置?

    创建表时,会应用下列设置参数:

    • join_use_nulls
    • max_rows_in_join
    • max_bytes_in_join
    • join_overflow_mode
    • join_any_take_last_row

    Join表不能在 GLOBAL JOIN操作中使用

    Join表创建及 查询时,允许使用join_use_nulls参数。如果使用不同的join_use_nulls设置,会导致表关联异常(取决于join的类型)。当使用函数 joinGet时,请在建表和查询语句中使用相同的 join_use_nulls 参数设置。

    数据存储?

    Join表的数据总是保存在内存中。当往表中插入行记录时,CH会将数据块保存在硬盘目录中,这样服务器重启时数据可以恢复。

    如果服务器非正常重启,保存在硬盘上的数据块会丢失或被损坏。这种情况下,需要手动删除被损坏的数据文件。

    内存表

    Memory 引擎以未压缩的形式将数据存储在 RAM 中。数据完全以读取时获得的形式存储。换句话说,从这张表中读取是很轻松的。并发数据访问是同步的。锁范围小:读写操作不会相互阻塞。不支持索引。查询是并行化的。在简单查询上达到最大速率(超过10 GB /秒),因为没有磁盘读取,不需要解压缩或反序列化数据。(值得注意的是,在许多情况下,与 MergeTree 引擎的性能几乎一样高)。重新启动服务器时,表中的数据消失,表将变为空。通常,使用此表引擎是不合理的。但是,它可用于测试,以及在相对较少的行(最多约100,000,000)上需要最高性能的查询。

    Memory 引擎是由系统用于临时表进行外部数据的查询(请参阅 ?外部数据用于请求处理? 部分),以及用于实现 GLOBAL IN(请参见 ?IN 运算符? 部分)。

    随机数生成表引擎

    随机数生成表引擎为指定的表模式生成随机数

    使用示例:

    • 测试时生成可复写的大表
    • 为复杂测试生成随机输入

    CH服务端的用法?

    ENGINE = GenerateRandom(random_seed, max_string_length, max_array_length)

    2. 查询数据:

    SELECT * FROM generate_engine_table LIMIT 3

    实现细节?

    • 以下特性不支持:
      • ALTER
      • SELECT ... SAMPLE
      • INSERT
      • Indices
      • Replication

    缓冲区

    缓冲数据写入 RAM 中,周期性地将数据刷新到另一个表。在读取操作时,同时从缓冲区和另一个表读取数据。

    Buffer(database, table, num_layers, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes)

    创建一个 ?merge.hits_buffer? 表,其结构与 ?merge.hits? 相同,并使用 Buffer 引擎。写入此表时,数据缓冲在 RAM 中,然后写入 ?merge.hits? 表。创建了16个缓冲区。如果已经过了100秒,或者已写入100万行,或者已写入100 MB数据,则刷新每个缓冲区的数据;或者如果同时已经过了10秒并且已经写入了10,000行和10 MB的数据。例如,如果只写了一行,那么在100秒之后,都会被刷新。但是如果写了很多行,数据将会更快地刷新。

    当服务器停止时,使用 DROP TABLE 或 DETACH TABLE,缓冲区数据也会刷新到目标表。

    可以为数据库和表名在单个引号中设置空字符串。这表示没有目的地表。在这种情况下,当达到数据刷新条件时,缓冲器被简单地清除。这可能对于保持数据窗口在内存中是有用的。

    从 Buffer 表读取时,将从缓冲区和目标表(如果有)处理数据。 请注意,Buffer 表不支持索引。换句话说,缓冲区中的数据被完全扫描,对于大缓冲区来说可能很慢。(对于目标表中的数据,将使用它支持的索引。)

    如果 Buffer 表中的列集与目标表中的列集不匹配,则会插入两个表中存在的列的子集。

    如果类型与 Buffer 表和目标表中的某列不匹配,则会在服务器日志中输入错误消息并清除缓冲区。 如果在刷新缓冲区时目标表不存在,则会发生同样的情况。

    如果需要为目标表和 Buffer 表运行 ALTER,我们建议先删除 Buffer 表,为目标表运行 ALTER,然后再次创建 Buffer 表。

    如果服务器异常重启,缓冲区中的数据将丢失。

    PREWHERE,FINAL 和 SAMPLE 对缓冲表不起作用。这些条件将传递到目标表,但不用于处理缓冲区中的数据。因此,我们建议只使用Buffer表进行写入,同时从目标表进行读取。

    将数据添加到缓冲区时,其中一个缓冲区被锁定。如果同时从表执行读操作,则会导致延迟。

    插入到 Buffer 表中的数据可能以不同的顺序和不同的块写入目标表中。因此,Buffer 表很难用于正确写入 CollapsingMergeTree。为避免出现问题,您可以将 ?num_layers? 设置为1。

    如果目标表是复制表,则在写入 Buffer 表时会丢失复制表的某些预期特征。数据部分的行次序和大小的随机变化导致数据不能去重,这意味着无法对复制表进行可靠的 ?exactly once? 写入。

    由于这些缺点,我们只建议在极少数情况下使用 Buffer 表。

    当在单位时间内从大量服务器接收到太多 INSERTs 并且在插入之前无法缓冲数据时使用 Buffer 表,这意味着这些 INSERTs 不能足够快地执行。

    请注意,一次插入一行数据是没有意义的,即使对于 Buffer 表也是如此。这将只产生每秒几千行的速度,而插入更大的数据块每秒可以产生超过一百万行(参见 ?性能? 部分)。

    字典

    Dictionary 引擎将字典数据展示为一个ClickHouse的表。

    例如,考虑使用一个具有以下配置的 products 字典:

    <dictionaries>
    <dictionary>
    <name>productsname>
    <source>
    <odbc>
    <table>productstable>
    <connection_string>DSN=some-db-serverconnection_string>
    odbc>
    source>
    <lifetime>
    <min>300min>
    <max>360max>
    lifetime>
    <layout>
    <flat/>
    layout>
    <structure>
    <id>
    <name>product_idname>
    id>
    <attribute>
    <name>titlename>
    <type>Stringtype>
    <null_value>null_value>
    attribute>
    structure>
    dictionary>
    dictionaries>
    ┌─name─────┬─type─┬─key────┬─attribute.names─┬─attribute.types─┬─bytes_allocated─┬─element_count─┬─source──────────┐
    │ products │ Flat │ UInt64 │ ['title'] │ ['String'] │ 23065376 │ 175032 │ ODBC: .products │
    └──────────┴──────┴────────┴─────────────────┴─────────────────┴─────────────────┴───────────────┴─────────────────┘

    示例:

    create table products (product_id UInt64, title String) Engine = Dictionary(products);

    CREATE TABLE products
    (
    product_id UInt64,
    title String,
    )
    ENGINE = Dictionary(products)

    看一看表中的内容。

    select * from products limit 1;

    SELECT *
    FROM products
    LIMIT 1

    选用的 Format 需要支持 INSERT 或 SELECT 。有关支持格式的完整列表,请参阅 格式。

    ClickHouse 不支持给 File 指定文件系统路径。它使用服务器配置中 路径 设定的文件夹。

    使用 File(Format) 创建表时,它会在该文件夹中创建空的子目录。当数据写入该表时,它会写到该子目录中的 data.Format 文件中。

    你也可以在服务器文件系统中手动创建这些子文件夹和文件,然后通过 ATTACH 将其创建为具有对应名称的表,这样你就可以从该文件中查询数据了。

    !!! 注意 "注意" 注意这个功能,因为 ClickHouse 不会跟踪这些文件在外部的更改。在 ClickHouse 中和 ClickHouse 外部同时写入会造成结果是不确定的。

    示例:

    1. 创建 file_engine_table 表:

    CREATE TABLE file_engine_table (name String, value UInt32) ENGINE=File(TabSeparated)

    3. 查询这些数据:

    SELECT * FROM file_engine_table

    在 Clickhouse-local 中的使用?

    使用 clickhouse-local 时,File 引擎除了 Format 之外,还可以接收文件路径参数。可以使用数字或名称来指定标准输入/输出流,例如 0 或 stdin1 或 stdout。 例如:

    $ echo -e "1,2\n3,4" | clickhouse-local -q "CREATE TABLE table (a Int64, b Int64) ENGINE = File(CSV, stdin); SELECT a, b FROM table; DROP TABLE table"

    数据会从 hits 数据库中表名匹配正则 ‘^WatchLog’ 的表中读取。

    除了数据库名,你也可以用一个返回字符串的常量表达式。例如, currentDatabase() 。

    正则表达式 — re2 (支持 PCRE 一个子集的功能),大小写敏感。 了解关于正则表达式中转义字符的说明可参看 ?match? 一节。

    当选择需要读的表时,Merge 表本身会被排除,即使它匹配上了该正则。这样设计为了避免循环。 当然,是能够创建两个相互无限递归读取对方数据的 Merge 表的,但这并没有什么意义。

    Merge 引擎的一个典型应用是可以像使用一张表一样使用大量的 TinyLog 表。

    示例 2 :

    我们假定你有一个旧表(WatchLog_old),你想改变数据分区了,但又不想把旧数据转移到新表(WatchLog_new)里,并且你需要同时能看到这两个表的数据。

    CREATE TABLE WatchLog_old(date Date, UserId Int64, EventType String, Cnt UInt64)
    ENGINE=MergeTree(date, (UserId, EventType), 8192);
    INSERT INTO WatchLog_old VALUES ('2018-01-01', 1, 'hit', 3);

    CREATE TABLE WatchLog_new(date Date, UserId Int64, EventType String, Cnt UInt64)
    ENGINE=MergeTree PARTITION BY date ORDER BY (UserId, EventType) SETTINGS index_granularity=8192;
    INSERT INTO WatchLog_new VALUES ('2018-01-02', 2, 'hit', 3);

    CREATE TABLE WatchLog as WatchLog_old ENGINE=Merge(currentDatabase(), '^WatchLog');

    SELECT *
    FROM WatchLog

    ┌───────date─┬─UserId─┬─EventType─┬─Cnt─┐
    │ 2018-01-01 │ 1 │ hit │ 3 │
    └────────────┴────────┴───────────┴─────┘
    ┌───────date─┬─UserId─┬─EventType─┬─Cnt─┐
    │ 2018-01-02 │ 2 │ hit │ 3 │
    └────────────┴────────┴───────────┴─────┘

    2. 用标准的 Python 3 工具库创建一个基本的 HTTP 服务并 启动它:

    from http.server import BaseHTTPRequestHandler, HTTPServer

    class CSVHTTPServer(BaseHTTPRequestHandler):
    def do_GET(self):
    self.send_response(200)
    self.send_header('Content-type', 'text/csv')
    self.end_headers()

    self.wfile.write(bytes('Hello,1\nWorld,2\n', "utf-8"))

    if __name__ == "__main__":
    server_address = ('127.0.0.1', 12345)
    HTTPServer(server_address, CSVHTTPServer).serve_forever()

    3. 查询请求:

    SELECT * FROM url_engine_table

    功能实现?

    • 读写操作都支持并发
    • 不支持:
      • ALTER 和 SELECT...SAMPLE 操作。
      • 索引。
      • 副本。

    视图

    用于构建视图(有关更多信息,请参阅 CREATE VIEW 查询)。 它不存储数据,仅存储指定的 SELECT 查询。 从表中读取时,它会运行此查询(并从查询中删除所有不必要的列)。