ClickHouse-3引擎
引擎
- 数据库引擎
- index
- 表引擎
数据库引擎
数据库引擎允许您处理数据表。
默认情况下,ClickHouse使用Atomic数据库引擎。它提供了可配置的table engines和SQL dialect。
您还可以使用以下数据库引擎:
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 ...]
引擎参数
host:port
— MySQL服务地址database
— MySQL数据库名称user
— MySQL用户名password
— MySQL用户密码
引擎配置
max_rows_in_buffer
— 允许数据缓存到内存中的最大行数(对于单个表和无法查询的缓存数据)。当超过行数时,数据将被物化。默认值:65505
。max_bytes_in_buffer
— 允许在内存中缓存数据的最大字节数(对于单个表和无法查询的缓存数据)。当超过行数时,数据将被物化。默认值:1048576
.max_rows_in_buffers
— 允许数据缓存到内存中的最大行数(对于数据库和无法查询的缓存数据)。当超过行数时,数据将被物化。默认值:65505
.max_bytes_in_buffers
— 允许在内存中缓存数据的最大字节数(对于数据库和无法查询的缓存数据)。当超过行数时,数据将被物化。默认值:1048576
.max_flush_data_time
— 允许数据在内存中缓存的最大毫秒数(对于数据库和无法查询的缓存数据)。当超过这个时间时,数据将被物化。默认值:1000
.max_wait_time_when_mysql_unavailable
— 当MySQL不可用时重试间隔(毫秒)。负值禁止重试。默认值:1000
.allows_query_when_mysql_lost
— 当mysql丢失时,允许查询物化表。默认值:0
(false
).
CREATE DATABASE mysql ENGINE = MaterializeMySQL('localhost:3306', 'db', 'user', '***')
SETTINGS
allows_query_when_mysql_lost=true,
max_wait_time_when_mysql_unavailable=10000;
MySQL服务器端配置
为了MaterializeMySQL
正确的工作,有一些强制性的MySQL
侧配置设置应该设置:
default_authentication_plugin = mysql_native_password
,因为MaterializeMySQL
只能使用此方法授权。gtid_mode = on
,因为要提供正确的MaterializeMySQL
复制,基于GTID的日志记录是必须的。注意,在打开这个模式On
时,你还应该指定enforce_gtid_consistency = on
。
虚拟列
当使用MaterializeMySQL
数据库引擎时,ReplacingMergeTree表与虚拟的_sign
和_version
列一起使用。
支持的数据类型
MySQL | ClickHouse |
---|---|
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
不支持直接INSERT
, DELETE
和UPDATE
查询. 但是,它们是在数据复制方面支持的:
-
MySQL的
INSERT
查询转换为INSERT
并携带_sign=1
. -
MySQL的
DELETE
查询转换为INSERT
并携带_sign=-1
. -
MySQL的
UPDATE
查询转换为INSERT
并携带_sign=-1
,INSERT
和_sign=1
.
查询MaterializeMySQL表
SELECT
查询MaterializeMySQL
表有一些细节:
-
如果
_version
在SELECT
中没有指定,则使用FINAL修饰符。所以只有带有MAX(_version)
的行才会被选中。 -
如果
_sign
在SELECT
中没有指定,则默认使用WHERE _sign=1
。因此,删除的行不会包含在结果集中。 -
结果包括列中的列注释,因为它们存在于SQL数据库表中。
Index Conversion
MySQL的PRIMARY KEY
和INDEX
子句在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;
+---+------+------+
| a | b | c |
+---+------+------+
| 2 | 222 | Wow! |
+---+------+------+
ClickHouse中的数据库,与MySQL服务器交换数据:
创建的数据库和表:
CREATE DATABASE mysql ENGINE = MaterializeMySQL('localhost:3306', 'db', 'user', '***');
SHOW TABLES FROM mysql;
┌─name─┐
│ test │
└──────┘
然后插入数据:
SELECT * FROM mysql.test;
┌─a─┬──b─┐
│ 1 │ 11 │
│ 2 │ 22 │
└───┴────┘
删除数据后,添加列并更新:
SELECT * FROM mysql.test;
┌─a─┬───b─┬─c────┐
│ 2 │ 222 │ Wow! │
└───┴─────┴──────┘
MaterializedPostgreSQL
使用PostgreSQL数据库表的初始数据转储创建ClickHouse数据库,并启动复制过程,即执行后台作业,以便在远程PostgreSQL数据库中的PostgreSQL数据库表上发生新更改时应用这些更改。
ClickHouse服务器作为PostgreSQL副本工作。它读取WAL并执行DML查询。DDL不是复制的,但可以处理(如下所述)。
创建数据库
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MaterializedPostgreSQL('host:port', ['database' | database], 'user', 'password') [SETTINGS ...]
Engine参数
host:port
— PostgreSQL服务地址database
— PostgreSQL数据库名user
— PostgreSQL用户名password
— 用户密码
设置
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;
必备条件
-
在postgresql配置文件中将wal_level设置为
logical
,将max_replication_slots
设置为2
。 -
每个复制表必须具有以下一个replica identity:
-
default (主键)
-
index
postgres# CREATE TABLE postgres_table (a Integer NOT NULL, b Integer, c Integer NOT NULL, d Integer, e Integer NOT NULL);
postgres# CREATE unique INDEX postgres_table_index on postgres_table(a, c, e);
postgres# ALTER TABLE postgres_table REPLICA IDENTITY USING INDEX postgres_table_index;
总是先检查主键。如果不存在,则检查索引(定义为副本标识索引)。 如果使用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;
注意
- TOAST不支持值转换。将使用数据类型的默认值。
使用示例
CREATE DATABASE postgresql_db
ENGINE = MaterializedPostgreSQL('postgres1:5432', 'postgres_database', 'postgres_user', 'postgres_password');
SELECT * FROM postgresql_db.postgres_table;
MySQL
MySQL引擎用于将远程的MySQL服务器中的表映射到ClickHouse中,并允许您对表进行INSERT
和SELECT
查询,以方便您在ClickHouse与MySQL之间进行数据交换
MySQL
数据库引擎会将对其的查询转换为MySQL语法并发送到MySQL服务器中,因此您可以执行诸如SHOW TABLES
或SHOW CREATE TABLE
之类的操作。
但您无法对其执行以下操作:
RENAME
CREATE TABLE
ALTER
创建数据库
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MySQL('host:port', ['database' | database], 'user', 'password')
引擎参数
host:port
— MySQL服务地址database
— MySQL数据库名称user
— MySQL用户名password
— MySQL用户密码
支持的数据类型
MySQL | ClickHouse |
---|---|
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;
使用示例
MySQL操作:
mysql> USE test;
Database changed
mysql> CREATE TABLE `mysql_table` (
-> `int_id` INT NOT NULL AUTO_INCREMENT,
-> `float` FLOAT NOT NULL,
-> PRIMARY KEY (`int_id`));
Query OK, 0 rows affected (0,09 sec)
mysql> insert into mysql_table (`int_id`, `float`) VALUES (1,2);
Query OK, 1 row affected (0,00 sec)
mysql> select * from mysql_table;
+------+-----+
| int_id | value |
+------+-----+
| 1 | 2 |
+------+-----+
1 row in set (0,00 sec)
ClickHouse中的数据库,与MySQL服务器交换数据:
CREATE DATABASE mysql_db ENGINE = MySQL('localhost:3306', 'test', 'my_user', 'user_password')
SHOW DATABASES
┌─name─────┐
│ default │
│ mysql_db │
│ system │
└──────────┘
SHOW TABLES FROM mysql_db
┌─name─────────┐
│ mysql_table │
└──────────────┘
SELECT * FROM mysql_db.mysql_table
┌─int_id─┬─value─┐
│ 1 │ 2 │
└────────┴───────┘
INSERT INTO mysql_db.mysql_table VALUES (3,4)
SELECT * FROM mysql_db.mysql_table
┌─int_id─┬─value─┐
│ 1 │ 2 │
│ 3 │ 4 │
└────────┴───────┘
Lazy
在最后一次访问之后,只在RAM中保存expiration_time_in_seconds
秒。只能用于*Log表。
它是为存储许多小的*Log表而优化的,对于这些表,访问之间有很长的时间间隔。
创建数据库
CREATE DATABASE testlazy ENGINE = Lazy(expiration_time_in_seconds);
Atomic
它支持非阻塞的DROP TABLE和RENAME TABLE查询和原子的EXCHANGE TABLES t1 AND t2查询。默认情况下使用Atomic
数据库引擎。
创建数据库
CREATE DATABASE test[ ENGINE = Atomic];
使用方式
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 = ...;
RENAME TABLES
RENAME
查询是在不更改UUID和移动表数据的情况下执行的。这些查询不会等待使用表的查询完成,而是会立即执行。
DROP/DETACH TABLES
在DROP TABLE
上,不删除任何数据,数据库Atomic
只是通过将元数据移动到/clickhouse_path/metadata_dropped/
将表标记为已删除,并通知后台线程。最终表数据删除前的延迟由database_atomic_delay_before_drop_table_sec设置指定。
可以使用SYNC
修饰符指定同步模式。使用database_atomic_wait_for_drop_and_detach_synchronously设置执行此操作。在本例中,DROP
等待运行 SELECT
, INSERT
和其他使用表完成的查询。表在不使用时将被实际删除。
EXCHANGE TABLES
EXCHANGE
以原子方式交换表。因此,不是这种非原子操作:
RENAME TABLE new_table TO tmp, old_table TO new_table, tmp TO old_table;
可以使用一个原子查询:
EXCHANGE TABLES new_table AND old_table;
ReplicatedMergeTree in Atomic Database
对于ReplicatedMergeTree表,建议不要在ZooKeeper和副本名称中指定engine-path的参数。在这种情况下,将使用配置的参数default_replica_path和default_replica_name。如果要显式指定引擎的参数,建议使用{uuid}宏。这是非常有用的,以便为ZooKeeper中的每个表自动生成唯一的路径。
SQLite
允许连接到SQLite数据库,并支持ClickHouse和SQLite交换数据, 执行 INSERT
和 SELECT
查询。
创建一个数据库
CREATE DATABASE sqlite_database
ENGINE = SQLite('db_path')
引擎参数
db_path
— SQLite 数据库文件的路径.
数据类型的支持
技术细节和建议
SQLite将整个数据库(定义、表、索引和数据本身)存储为主机上的单个跨平台文件。在写入过程中,SQLite会锁定整个数据库文件,因此写入操作是顺序执行的。读操作可以是多任务的。 SQLite不需要服务管理(如启动脚本)或基于GRANT
和密码的访问控制。访问控制是通过授予数据库文件本身的文件系统权限来处理的。
使用示例
数据库在ClickHouse,连接到SQLite:
CREATE DATABASE sqlite_db ENGINE = SQLite('sqlite.db');
SHOW TABLES FROM sqlite_db;
┌──name───┐
│ table1 │
│ table2 │
└─────────┘
展示数据表中的内容:
SELECT * FROM sqlite_db.table1;
┌─col1──┬─col2─┐
│ line1 │ 1 │
│ line2 │ 2 │
│ line3 │ 3 │
└───────┴──────┘
从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;
┌─col1──┬─col2─┐
│ line1 │ 1 │
│ line2 │ 2 │
│ line3 │ 3 │
│ text │ 10 │
└───────┴──────┘
PostgreSQL
允许连接到远程PostgreSQL服务。支持读写操作(SELECT
和INSERT
查询),以在ClickHouse和PostgreSQL之间交换数据。
在SHOW TABLES
和DESCRIBE TABLE
查询的帮助下,从远程PostgreSQL实时访问表列表和表结构。
支持表结构修改(ALTER TABLE ... ADD|DROP COLUMN
)。如果use_table_cache
参数(参见下面的引擎参数)设置为1
,则会缓存表结构,不会检查是否被修改,但可以用DETACH
和ATTACH
查询进行更新。
创建数据库
CREATE DATABASE test_database
ENGINE = PostgreSQL('host:port', 'database', 'user', 'password'[, `use_table_cache`]);
引擎参数
host:port
— PostgreSQL服务地址database
— 远程数据库名次user
— PostgreSQL用户名称password
— PostgreSQL用户密码schema
- PostgreSQL 模式use_table_cache
— 定义数据库表结构是否已缓存或不进行。可选的。默认值:0
.
支持的数据类型
PostgerSQL | ClickHouse |
---|---|
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);
SHOW DATABASES;
┌─name──────────┐
│ default │
│ test_database │
│ system │
└───────────────┘
SHOW TABLES FROM test_database;
┌─name───────┐
│ test_table │
└────────────┘
从PostgreSQL表中读取数据:
SELECT * FROM test_database.test_table;
┌─id─┬─value─┐
│ 1 │ 2 │
└────┴───────┘
将数据写入PostgreSQL表:
INSERT INTO test_database.test_table VALUES (3,4);
SELECT * FROM test_database.test_table;
┌─int_id─┬─value─┐
│ 1 │ 2 │
│ 3 │ 4 │
└────────┴───────┘
在PostgreSQL中修改了表结构:
postgre> ALTER TABLE test_table ADD COLUMN data Text
当创建数据库时,参数use_table_cache
被设置为1
,ClickHouse中的表结构被缓存,因此没有被修改:
DESCRIBE TABLE test_database.test_table;
┌─name───┬─type──────────────┐
│ id │ Nullable(Integer) │
│ value │ Nullable(Integer) │
└────────┴───────────────────┘
分离表并再次附加它之后,结构被更新了:
DETACH TABLE test_database.test_table;
ATTACH TABLE test_database.test_table;
DESCRIBE TABLE test_database.test_table;
┌─name───┬─type──────────────┐
│ id │ Nullable(Integer) │
│ value │ Nullable(Integer) │
│ data │ Nullable(String) │
└────────┴───────────────────┘
Replicated
该引擎基于Atomic引擎。它支持通过将DDL日志写入ZooKeeper并在给定数据库的所有副本上执行的元数据复制。
一个ClickHouse服务器可以同时运行和更新多个复制的数据库。但是同一个复制的数据库不能有多个副本。
创建数据库
CREATE DATABASE testdb ENGINE = Replicated('zoo_path', 'shard_name', 'replica_name') [SETTINGS ...]
引擎参数
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_name
和replica_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}');
运行DDL:
CREATE TABLE r.rmt (n UInt64) ENGINE=ReplicatedMergeTree ORDER BY n;
┌─────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 │
└──────────────────────┴─────────┴───────┴─────────────────────┴──────────────────┘
显示系统表:
SELECT cluster, shard_num, replica_num, host_name, host_address, port, is_local
FROM system.clusters WHERE cluster='r';
┌─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 │
└─────────┴───────────┴─────────────┴───────────┴──────────────┴──────┴──────────┘
创建分布式表并插入数据:
node2 :) CREATE TABLE r.d (n UInt64) ENGINE=Distributed('r','r','rmt', n % 2);
node3 :) INSERT INTO r SELECT * FROM numbers(10);
node1 :) SELECT materialize(hostName()) AS host, groupArray(n) FROM r.d GROUP BY host;
┌─hosts─┬─groupArray(n)─┐
│ node1 │ [1,3,5,7,9] │
│ node2 │ [0,2,4,6,8] │
└───────┴───────────────┘
向一台主机添加副本:
node4 :) CREATE DATABASE r ENGINE=Replicated('some/path/r','other_shard','r2');
集群配置如下所示:
┌─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 │
└─────────┴───────────┴─────────────┴───────────┴──────────────┴──────┴──────────┘
分布式表也将从新主机获取数据:
node2 :) SELECT materialize(hostName()) AS host, groupArray(n) FROM r.d GROUP BY host;
┌─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万行)并在以后整体读取它们时,该类型的引擎是最有效的。
该类型的引擎:
集成引擎
用于与其他的数据存储与处理系统集成的引擎。 该类型的引擎:
用于其他特定功能的引擎
该类型的引擎:
虚拟列
虚拟列是表引擎组成的一部分,它在对应的表引擎的源代码中定义。
您不能在 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, ...]
对于以上参数的描述,可参考 CREATE 语句 的描述 。
子句
-
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
- 如果要 选择与排序键不同的主键,在这里指定,可选项。默认情况下主键跟排序键(由
ORDER BY
子句指定)相同。 因此,大部分情况下不需要再专门指定一个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'
指定了当满足条件(到达指定时间)时所要执行的动作:移除过期的行,还是将数据片段(如果数据片段中的所有行都满足表达式的话)移动到指定的磁盘(TO DISK 'xxx'
) 或 卷(TO VOLUME 'xxx'
)。默认的规则是移除(DELETE
)。可以在列表中指定多个规则,但最多只能有一个DELETE
的规则。更多细节,请查看 表和列的 TTL
-
SETTINGS
— 控制MergeTree
行为的额外参数,可选项:index_granularity
— 索引粒度。索引中相邻的『标记』间的数据行数。默认值8192 。参考数据存储。index_granularity_bytes
— 索引粒度,以字节为单位,默认值: 10Mb。如果想要仅按数据行数限制索引粒度, 请设置为0(不建议)。min_index_granularity_bytes
- 允许的最小数据粒度,默认值:1024b。该选项用于防止误操作,添加了一个非常低索引粒度的表。参考数据存储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 会存储更少的数据。更多信息参考[服务配置参数](Server Settings | ClickHouse Documentation)这章中的 设置描述 。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
字节。<a name="mergetree_setting-merge_with_ttl_timeout"></a>
merge_with_ttl_timeout
— TTL合并频率的最小间隔时间,单位:秒。默认值: 86400 (1 天)。write_final_mark
— 是否启用在数据片段尾部写入最终索引标记。默认值: 1(不要关闭)。merge_max_block_size
— 在块中进行合并操作时的最大行数限制。默认值:8192storage_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
在这个例子中,我们设置了按月进行分区。
同时我们设置了一个按用户 ID 哈希的抽样表达式。这使得您可以对该表中每个 CounterID
和 EventDate
的数据伪随机分布。如果您在查询时指定了 SAMPLE 子句。 ClickHouse会返回对于用户子集的一个均匀的伪随机数据采样。
index_granularity
可省略因为 8192 是默认设置 。
已弃用的建表方法
数据存储
表由按主键排序的数据片段(DATA PART)组成。
当数据被插入到表中时,会创建多个数据片段并按主键的字典序排序。例如,主键是 (CounterID, Date)
时,片段中数据首先按 CounterID
排序,具有相同 CounterID
的部分按 Date
排序。
不同分区的数据会被分成不同的片段,ClickHouse 在后台合并数据片段以便更高效存储。不同分区的数据片段不会进行合并。合并机制并不保证具有相同主键的行全都合并到同一个数据片段中。
数据片段可以以 Wide
或 Compact
格式存储。在 Wide
格式下,每一列都会在文件系统中存储为单独的文件,在 Compact
格式下所有列都存储在一个文件中。Compact
格式可以提高插入量少插入频率频繁时的性能。
数据存储格式由 min_bytes_for_wide_part
和 min_rows_for_wide_part
表引擎参数控制。如果数据片段中的字节数或行数少于相应的设置值,数据片段会以 Compact
格式存储,否则会以 Wide
格式存储。
每个数据片段被逻辑的分割成颗粒(granules)。颗粒是 ClickHouse 中进行数据查询时的最小不可分割数据集。ClickHouse 不会对行或值进行拆分,所以每个颗粒总是包含整数个行。每个颗粒的第一行通过该行的主键值进行标记, ClickHouse 会为每个数据片段创建一个索引文件来存储这些标记。对于每列,无论它是否包含在主键当中,ClickHouse 都会存储类似标记。这些标记让您可以在列文件中直接找到数据。
颗粒的大小通过表引擎参数 index_granularity
和 index_granularity_bytes
控制。颗粒的行数的在 [1, index_granularity]
范围中,这取决于行的大小。如果单行的大小超过了 index_granularity_bytes
设置的值,那么一个颗粒的大小会超过 index_granularity_bytes
。在这种情况下,颗粒的大小等于该行的大小。
主键和索引在查询中的表现
我们以 (CounterID, Date)
以主键。排序好的索引的图示会是下面这样:
全部数据 : [-------------------------------------------------------------------------]
CounterID: [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
Date: [1111111222222233331233211111222222333211111112122222223111112223311122333]
标记: | | | | | | | | | | |
a,1 a,2 a,3 b,3 e,2 e,3 g,1 h,2 i,1 i,3 l,3
标记号: 0 1 2 3 4 5 6 7 8 9 10
如果指定查询如下:
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 KEY
与ORDER 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
这种情况下,这些查询:
SELECT count() FROM table WHERE EventDate = toDate(now()) AND CounterID = 34
SELECT count() FROM table WHERE EventDate = toDate(now()) AND (CounterID = 34 OR CounterID = 42)
SELECT count() FROM table WHERE ((EventDate >= toDate('2014-01-01') AND EventDate <= toDate('2014-01-31')) OR EventDate = toDate('2014-05-01')) AND CounterID IN (101500, 731962, 160656) AND (CounterID = 101500 OR EventDate != toDate('2014-05-01'))
ClickHouse 会依据主键索引剪掉不符合的数据,依据按月分区的分区键剪掉那些不包含符合数据的分区。
上文的查询显示,即使索引用于复杂表达式,因为读表操作经过优化,所以使用索引不会比完整扫描慢。
下面这个例子中,不会使用索引。
SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'
要检查 ClickHouse 执行一个查询时能否使用索引,可设置 force_index_by_date 和 force_primary_key 。
使用按月分区的分区列允许只读取包含适当日期区间的数据块,这种情况下,数据块会包含很多天(最多整月)的数据。在块中,数据按主键排序,主键第一列可能不包含日期。因此,仅使用日期而没有用主键字段作为条件的查询将会导致需要读取超过这个指定日期以外的数据。
部分单调主键的使用
考虑这样的场景,比如一个月中的天数。它们在一个月的范围内形成一个单调序列 ,但如果扩展到更大的时间范围它们就不再单调了。这就是一个部分单调序列。如果用户使用部分单调的主键创建表,ClickHouse同样会创建一个稀疏索引。当用户从这类表中查询数据时,ClickHouse 会对查询条件进行分析。如果用户希望获取两个索引标记之间的数据并且这两个标记在一个月以内,ClickHouse 可以在这种特殊情况下使用到索引,因为它可以计算出查询参数与索引标记之间的距离。
如果查询参数范围内的主键不是单调序列,那么 ClickHouse 无法使用索引。在这种情况下,ClickHouse 会进行全表扫描。
ClickHouse 在任何主键代表一个部分单调序列的情况下都会使用这个逻辑。
跳数索引
此索引在 CREATE
语句的列部分里定义。
INDEX index_name expr TYPE type(...) GRANULARITY granularity_value
*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()
...
上例中的索引能让 ClickHouse 执行下面这些查询时减少读取数据量。
SELECT count() FROM table WHERE s < 'z'
SELECT count() FROM table WHERE u64 * i32 == 10 AND u64 * length(s) >= 1234
可用的索引类型
-
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*
,Enum
,Date
,DateTime
,String
,FixedString
,Array
,LowCardinality
,Nullable
。
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
函数支持
WHERE 子句中的条件可以包含对某列数据进行运算的函数表达式,如果列是索引的一部分,ClickHouse会在执行函数时尝试使用索引。不同的函数对索引的支持是不同的。
set
索引会对所有函数生效,其他索引对函数的生效情况见下表
函数 (操作符) / 索引 | primary key | minmax | ngrambf_v1 | tokenbf_v1 | bloom_filter |
---|---|---|---|---|---|
equals (=, ==) | ✔ | ✔ | ✔ | ✔ | ✔ |
notEquals(!=, <>) | ✔ | ✔ | ✔ | ✔ | ✔ |
like | ✔ | ✔ | ✔ | ✔ | ✔ |
notLike | ✔ | ✔ | ✗ | ✗ | ✗ |
startsWith | ✔ | ✔ | ✔ | ✔ | ✗ |
endsWith | ✗ | ✗ | ✔ | ✔ | ✗ |
multiSearchAny | ✗ | ✗ | ✔ | ✗ | ✗ |
in | ✔ | ✔ | ✔ | ✔ | ✔ |
notIn | ✔ | ✔ | ✔ | ✔ | ✔ |
less (\<) | ✔ | ✔ | ✗ | ✗ | ✗ |
greater (>) | ✔ | ✔ | ✗ | ✗ | ✗ |
lessOrEquals (\<=) | ✔ | ✔ | ✗ | ✗ | ✗ |
greaterOrEquals (>=) | ✔ | ✔ | ✗ | ✗ | ✗ |
empty | ✔ | ✔ | ✗ | ✗ | ✗ |
notEmpty | ✔ | ✔ | ✗ | ✗ | ✗ |
hasToken | ✗ | ✗ | ✗ | ✔ | ✗ |
常量参数小于 ngram 大小的函数不能使用 ngrambf_v1
进行查询优化。
!!! note "注意" 布隆过滤器可能会包含不符合条件的匹配,所以 ngrambf_v1
, tokenbf_v1
和 bloom_filter
索引不能用于结果返回为假的函数,例如:
- 可以用来优化的场景
s LIKE '%test%'
NOT s NOT LIKE '%test%'
s = 1
NOT s != 1
startsWith(s, 'test')
- 不能用来优化的场景
NOT s LIKE '%test%'
s NOT LIKE '%test%'
NOT s = 1
s != 1
NOT startsWith(s, 'test')
并发数据访问
对于表的并发访问,我们使用多版本机制。换言之,当一张表同时被读和更新时,数据从当前查询到的一组片段中读取。没有冗长的的锁。插入不会阻碍读取。
对表的读操作是自动并行的。
列和表的 TTL
TTL用于设置值的生命周期,它既可以为整张表设置,也可以为每个列字段单独设置。表级别的 TTL 还会指定数据在磁盘和卷上自动转移的逻辑。
TTL 表达式的计算结果必须是 日期 或 日期时间 类型的字段。
示例:
TTL time_column
TTL time_column + interval
要定义interval
, 需要使用 时间间隔 操作符。
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
表可以设置一个用于移除过期行的表达式,以及多个用于在磁盘或卷上自动转移数据片段的表达式。当表中的行过期时,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 规则的类型紧跟在每个 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;
创建一张表,设置过期的列会被聚合。列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);
删除数据
ClickHouse 在数据片段合并时会删除掉过期的数据。
当ClickHouse发现数据过期时, 它将会执行一个计划外的合并。要控制这类合并的频率, 您可以设置 merge_with_ttl_timeout
。如果该值被设置的太低, 它将引发大量计划外的合并,这可能会消耗大量资源。
如果在两次合并的时间间隔中执行 SELECT
查询, 则可能会得到过期的数据。为了避免这种情况,可以在 SELECT
之前使用 OPTIMIZE 。
使用多个块设备进行数据存储
介绍
MergeTree 系列表引擎可以将数据存储在多个块设备上。这对某些可以潜在被划分为“冷”“热”的表来说是很有用的。最新数据被定期的查询但只需要很小的空间。相反,详尽的历史数据很少被用到。如果有多块磁盘可用,那么“热”的数据可以放置在快速的磁盘上(比如 NVMe 固态硬盘或内存),“冷”的数据可以放在相对较慢的磁盘上(比如机械硬盘)。
数据片段是 MergeTree
引擎表的最小可移动单元。属于同一个数据片段的数据被存储在同一块磁盘上。数据片段会在后台自动的在磁盘间移动,也可以通过 ALTER 查询来移动。
术语
-
磁盘 — 挂载到文件系统的块设备
-
默认磁盘 — 在服务器设置中通过 path 参数指定的数据存储
-
卷 — 相同磁盘的顺序列表 (类似于 JBOD)
-
存储策略 — 卷的集合及他们之间的数据移动规则
以上名称的信息在Clickhouse中系统表system.storage_policies和system.disks体现。为了应用存储策略,可以在建表时使用
storage_policy
设置。
配置
磁盘、卷和存储策略应当在主配置文件 config.xml
或 config.d
目录中的独立文件中的 <storage_configuration>
标签内定义。
配置结构:
<storage_configuration>
<disks>
<disk_name_1> <!-- disk name -->
<path>/mnt/fast_ssd/clickhouse/</path>
</disk_name_1>
<disk_name_2>
<path>/mnt/hdd1/clickhouse/</path>
<keep_free_space_bytes>10485760</keep_free_space_bytes>
</disk_name_2>
<disk_name_3>
<path>/mnt/hdd2/clickhouse/</path>
<keep_free_space_bytes>10485760</keep_free_space_bytes>
</disk_name_3>
...
</disks>
...
</storage_configuration>
标签:
<disk_name_N>
— 磁盘名,名称必须与其他磁盘不同.path
— 服务器将用来存储数据 (data
和shadow
目录) 的路径, 应当以 ‘/’ 结尾.keep_free_space_bytes
— 需要保留的剩余磁盘空间.
磁盘定义的顺序无关紧要。
存储策略配置:
<storage_configuration>
...
<policies>
<policy_name_1>
<volumes>
<volume_name_1>
<disk>disk_name_from_disks_configuration</disk>
<max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
</volume_name_1>
<volume_name_2>
<!-- configuration -->
</volume_name_2>
<!-- more volumes -->
</volumes>
<move_factor>0.2</move_factor>
</policy_name_1>
<policy_name_2>
<!-- configuration -->
</policy_name_2>
<!-- more policies -->
</policies>
...
</storage_configuration>
标签:
policy_name_N
— 策略名称,不能重复。volume_name_N
— 卷名称,不能重复。disk
— 卷中的磁盘。max_data_part_size_bytes
— 卷中的磁盘可以存储的数据片段的最大大小。move_factor
— 当可用空间少于这个因子时,数据将自动的向下一个卷(如果有的话)移动 (默认值为 0.1)。prefer_not_to_merge
- 禁止在这个卷中进行数据合并。该选项启用时,对该卷的数据不能进行合并。这个选项主要用于慢速磁盘。
配置示例:
<storage_configuration>
...
<policies>
<hdd_in_order> <!-- policy name -->
<volumes>
<single> <!-- volume name -->
<disk>disk1</disk>
<disk>disk2</disk>
</single>
</volumes>
</hdd_in_order>
<moving_from_ssd_to_hdd>
<volumes>
<hot>
<disk>fast_ssd</disk>
<max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
</hot>
<cold>
<disk>disk1</disk>
</cold>
</volumes>
<move_factor>0.2</move_factor>
</moving_from_ssd_to_hdd>
<small_jbod_with_external_no_merges>
<volumes>
<main>
<disk>jbod1</disk>
</main>
<external>
<disk>external</disk>
<prefer_not_to_merge>true</prefer_not_to_merge>
</external>
</volumes>
</small_jbod_with_external_no_merges>
</policies>
...
</storage_configuration>
在给出的例子中, hdd_in_order
策略实现了 循环制 方法。因此这个策略只定义了一个卷(single
),数据片段会以循环的顺序全部存储到它的磁盘上。当有多个类似的磁盘挂载到系统上,但没有配置 RAID 时,这种策略非常有用。请注意一个每个独立的磁盘驱动都并不可靠,您可能需要用3份或更多的复制份数来补偿它。
如果在系统中有不同类型的磁盘可用,可以使用 moving_from_ssd_to_hdd
。hot
卷由 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'
default
存储策略意味着只使用一个卷,这个卷只包含一个在 <path>
中定义的磁盘。您可以使用[ALTER TABLE ... MODIFY SETTING]来修改存储策略,新的存储策略应该包含所有以前的磁盘和卷,并使用相同的名称。
可以通过 background_move_pool_size 设置调整执行后台任务的线程数。
详细说明
对于 MergeTree
表,数据通过以下不同的方式写入到磁盘当中:
- 插入(
INSERT
查询) - 后台合并和数据变异
- 从另一个副本下载
- ALTER TABLE … FREEZE PARTITION 冻结分区
除了数据变异和冻结分区以外的情况下,数据按照以下逻辑存储到卷或磁盘上:
- 首个卷(按定义顺序)拥有足够的磁盘空间存储数据片段(
unreserved_space > current_part_size
)并且允许存储给定数据片段的大小(max_data_part_size_bytes > current_part_size
) - 在这个数据卷内,紧挨着先前存储数据的那块磁盘之后的磁盘,拥有比数据片段大的剩余空间。(
unreserved_space - keep_free_space_bytes > current_part_size
)
更进一步,数据变异和分区冻结使用的是 硬链接。不同磁盘之间的硬链接是不支持的,所以在这种情况下数据片段都会被存储到原来的那一块磁盘上。
在后台,数据片段基于剩余空间(move_factor
参数)根据卷在配置文件中定义的顺序进行转移。数据永远不会从最后一个移出也不会从第一个移入。可以通过系统表 system.part_log (字段 type = MOVE_PART
) 和 system.parts (字段 path
和 disk
) 来监控后台的移动情况。具体细节可以通过服务器日志查看。
用户可以通过 ALTER TABLE … MOVE PART|PARTITION … TO VOLUME|DISK … 强制移动一个数据片段或分区到另外一个卷,所有后台移动的限制都会被考虑在内。这个查询会自行启动,无需等待后台操作完成。如果没有足够的可用空间或任何必须条件没有被满足,用户会收到报错信息。
数据移动不会妨碍到数据复制。也就是说,同一张表的不同副本可以指定不同的存储策略。
在后台合并和数据变异之后,旧的数据片段会在一定时间后被移除 (old_parts_lifetime
)。在这期间,他们不能被移动到其他的卷或磁盘。也就是说,直到数据片段被完全移除,它们仍然会被磁盘占用空间计算在内。
使用S3进行数据存储
MergeTree
系列表引擎允许使用S3存储数据,需要修改磁盘类型为S3
。
示例配置:
<storage_configuration>
...
<disks>
<s3>
<type>s3</type>
<endpoint>https://storage.yandexcloud.net/my-bucket/root-path/</endpoint>
<access_key_id>your_access_key_id</access_key_id>
<secret_access_key>your_secret_access_key</secret_access_key>
<region></region>
<server_side_encryption_customer_key_base64>your_base64_encoded_customer_key</server_side_encryption_customer_key_base64>
<proxy>
<uri>http://proxy1</uri>
<uri>http://proxy2</uri>
</proxy>
<connect_timeout_ms>10000</connect_timeout_ms>
<request_timeout_ms>5000</request_timeout_ms>
<retry_attempts>10</retry_attempts>
<single_read_retries>4</single_read_retries>
<min_bytes_for_seek>1000</min_bytes_for_seek>
<metadata_path>/var/lib/clickhouse/disks/s3/</metadata_path>
<cache_enabled>true</cache_enabled>
<cache_path>/var/lib/clickhouse/disks/s3/cache/</cache_path>
<skip_access_check>false</skip_access_check>
</s3>
</disks>
...
</storage_configuration>
必须的参数:
endpoint
- S3的结点URL,以path
或virtual 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/<disk_name>/
cache_enabled
- 是否允许缓存标记和索引文件。默认值为true
。cache_path
- 本地缓存标记和索引文件的路径。默认值为/var/lib/clickhouse/disks/<disk_name>/cache/
。skip_access_check
- 如果为true
,Clickhouse启动时不检查磁盘是否可用。默认为false
。server_side_encryption_customer_key_base64
- 如果指定该项的值,请求时会加上为了访问SSE-C加密数据而必须的头信息。
S3磁盘也可以设置冷热存储:
<storage_configuration>
...
<disks>
<s3>
<type>s3</type>
<endpoint>https://storage.yandexcloud.net/my-bucket/root-path/</endpoint>
<access_key_id>your_access_key_id</access_key_id>
<secret_access_key>your_secret_access_key</secret_access_key>
</s3>
</disks>
<policies>
<s3_main>
<volumes>
<main>
<disk>s3</disk>
</main>
</volumes>
</s3_main>
<s3_cold>
<volumes>
<main>
<disk>default</disk>
</main>
<external>
<disk>s3</disk>
</external>
</volumes>
<move_factor>0.2</move_factor>
</s3_cold>
</policies>
...
</storage_configuration>
指定了cold
选项后,本地磁盘剩余空间如果小于move_factor * disk_size
,或有TTL设置时,数据就会定时迁移至S3了。
虚拟列
_part
- 分区名称。_part_index
- 作为请求的结果,按顺序排列的分区数。_partition_id
— 分区名称。_part_uuid
- 唯一部分标识符(如果 MergeTree 设置assign_part_uuids
已启用)。_partition_value
—partition by
表达式的值(元组)。_sample_factor
- 采样因子(来自请求)。
VersionedCollapsingMergeTree
这个引擎:
- 允许快速写入不断变化的对象状态。
- 删除后台中的旧对象状态。 这显著降低了存储体积。
请参阅部分 崩溃 有关详细信息。
引擎继承自 MergeTree 并将折叠行的逻辑添加到合并数据部分的算法中。 VersionedCollapsingMergeTree
用于相同的目的 折叠树 但使用不同的折叠算法,允许以多个线程的任何顺序插入数据。 特别是, Version
列有助于正确折叠行,即使它们以错误的顺序插入。 相比之下, CollapsingMergeTree
只允许严格连续插入。
创建表
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(sign, version)
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
有关查询参数的说明,请参阅 查询说明.
引擎参数
VersionedCollapsingMergeTree(sign, version)
-
sign
— 指定行类型的列名:1
是一个 “state” 行,-1
是一个 “cancel” 行列数据类型应为
Int8
. -
version
— 指定对象状态版本的列名。列数据类型应为
UInt*
.
查询 Clauses
当创建一个 VersionedCollapsingMergeTree
表时,跟创建一个 MergeTree
表的时候需要相同 Clause
不推荐使用的创建表的方法
:::info "注意"
不要在新项目中使用此方法。 如果可能,请将旧项目切换到上述方法。
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)
所有的参数,除了 sign
和 version
具有相同的含义 MergeTree
.
-
sign
— 指定行类型的列名:1
是一个 “state” 行,-1
是一个 “cancel” 划Column Data Type —
Int8
. -
version
— 指定对象状态版本的列名。列数据类型应为
UInt*
.
折叠
数据
考虑一种情况,您需要为某个对象保存不断变化的数据。 对于一个对象有一行,并在发生更改时更新该行是合理的。 但是,对于数据库管理系统来说,更新操作非常昂贵且速度很慢,因为它需要重写存储中的数据。 如果需要快速写入数据,则不能接受更新,但可以按如下顺序将更改写入对象。
使用 Sign
列写入行时。 如果 Sign = 1
这意味着该行是一个对象的状态(让我们把它称为 “state” 行)。 如果 Sign = -1
它指示具有相同属性的对象的状态的取消(让我们称之为 “cancel” 行)。 还可以使用 Version
列,它应该用单独的数字标识对象的每个状态。
例如,我们要计算用户在某个网站上访问了多少页面以及他们在那里的时间。 在某个时间点,我们用用户活动的状态写下面的行:
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 |
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
在稍后的某个时候,我们注册用户活动的变化,并用以下两行写入它。
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 |
│ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 |
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
第一行取消对象(用户)的先前状态。 它应该复制已取消状态的所有字段,除了 Sign
.
第二行包含当前状态。
因为我们只需要用户活动的最后一个状态,行
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 |
│ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 |
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
可以删除,折叠对象的无效(旧)状态。 VersionedCollapsingMergeTree
在合并数据部分时执行此操作。
要了解为什么每次更改都需要两行,请参阅 算法.
使用注意事项
- 写入数据的程序应该记住对象的状态以取消它。 该 “cancel” 字符串应该是 “state” 与相反的字符串
Sign
. 这增加了存储的初始大小,但允许快速写入数据。 - 列中长时间增长的数组由于写入负载而降低了引擎的效率。 数据越简单,效率就越高。
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
.
聚合 count
, sum
和 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 |
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
创建表:
CREATE TABLE UAct
(
UserID UInt64,
PageViews UInt8,
Duration UInt8,
Sign Int8,
Version UInt8
)
ENGINE = VersionedCollapsingMergeTree(Sign, Version)
ORDER BY UserID
插入数据:
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1, 1)
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1, 1),(4324182021466249494, 6, 185, 1, 2)
我们用两个 INSERT
查询以创建两个不同的数据部分。 如果我们使用单个查询插入数据,ClickHouse将创建一个数据部分,并且永远不会执行任何合并。
获取数据:
SELECT * FROM UAct
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
我们在这里看到了什么,折叠的部分在哪里? 我们使用两个创建了两个数据部分 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
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Version─┐
│ 4324182021466249494 │ 6 │ 185 │ 2 │
└─────────────────────┴───────────┴──────────┴─────────┘
如果我们不需要聚合,并希望强制折叠,我们可以使用 FINAL
修饰符 FROM
条款
SELECT * FROM UAct FINAL
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
这是一个非常低效的方式来选择数据。 不要把它用于数据量大的表。
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, ...]
建表语句的详细说明请参见 创建表
含有Graphite数据集的表应该包含以下的数据列:
-
指标名称(Graphite sensor),数据类型:
String
-
指标的时间度量,数据类型:
DateTime
-
指标的值,数据类型:任意数值类型
-
指标的版本号,数据类型: 任意数值类型
CH以最大的版本号保存行记录,若版本号相同,保留最后写入的数据。
以上列必须设置在汇总参数配置中。
GraphiteMergeTree 参数
config_section
- 配置文件中标识汇总规则的节点名称
建表语句
在创建 GraphiteMergeTree
表时,需要采用和 clauses 相同的语句,就像创建 MergeTree
一样。
已废弃的建表语句
汇总配置的参数
汇总的配置参数由服务器配置的 graphite_rollup 参数定义。参数名称可以是任意的。允许为多个不同表创建多组配置并使用。
汇总配置的结构如下: 所需的列 模式Patterns
所需的列
path_column_name
— 保存指标名称的列名 (Graphite sensor). 默认值:Path
.time_column_name
— 保存指标时间度量的列名. Default value:Time
.value_column_name
— The name of the column storing the value of the metric at the time set intime_column_name
.默认值:Value
.version_column_name
- 保存指标的版本号列. 默认值:Timestamp
.
模式Patterns
patterns
的结构:
pattern
regexp
function
pattern
regexp
age + precision
...
pattern
regexp
function
age + precision
...
pattern
...
default
function
age + precision
...
!!! 注意 "Attention" 模式必须严格按顺序配置:
1. 不含`function` or `retention`的Patterns
1. 同时含有`function` and `retention`的Patterns
1. `default`的Patterns.
CH在处理行记录时,会检查 pattern
节点的规则。每个 pattern
(含default
)节点可以包含 function
用于聚合操作,或retention
参数,或者两者都有。如果指标名称和 regexp
相匹配,相应 pattern
的规则会生效;否则,使用 default
节点的规则。
pattern
和 default
节点的字段设置:
regexp
– 指标名的pattern.age
– 数据的最小存活时间(按秒算).precision
– 按秒来衡量数据存活时间时的精确程度. 必须能被86400整除 (一天的秒数).function
– 对于存活时间在[age, age + precision]
之内的数据,需要使用的聚合函数
配置示例
<graphite_rollup>
<version_column_name>Version</version_column_name>
<pattern>
<regexp>click_cost</regexp>
<function>any</function>
<retention>
<age>0</age>
<precision>5</precision>
</retention>
<retention>
<age>86400</age>
<precision>60</precision>
</retention>
</pattern>
<default>
<function>max</function>
<retention>
<age>0</age>
<precision>60</precision>
</retention>
<retention>
<age>3600</age>
<precision>300</precision>
</retention>
<retention>
<age>86400</age>
<precision>3600</precision>
</retention>
</default>
</graphite_rollup>
AggregatingMergeTree
该引擎继承自 MergeTree,并改变了数据片段的合并逻辑。 ClickHouse 会将一个数据片段内所有具有相同主键(准确的说是 排序键)的行替换成一行,这一行会存储一系列聚合函数的状态。
可以使用 AggregatingMergeTree
表来做增量数据的聚合统计,包括物化视图的数据聚合。
引擎使用以下类型来处理所有列:
AggregatingMergeTree
适用于能够按照一定的规则缩减行数的情况。
建表
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = AggregatingMergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[TTL expr]
[SETTINGS name=value, ...]
语句参数的说明,请参阅 建表语句描述。
子句
创建 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.visits
表中插入数据。
INSERT INTO test.visits ...
数据会同时插入到表和视图中,并且视图 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, ...]
请求参数的描述,参考请求参数。
CollapsingMergeTree 参数
-
sign
— 类型列的名称:1
是«状态»行,-1
是«取消»行。列数据类型 —
Int8
。
子句
创建 CollapsingMergeTree
表时,需要与创建 MergeTree
表时相同的子句。
已弃用的建表方法
折叠
数据
考虑你需要为某个对象保存不断变化的数据的情景。似乎为一个对象保存一行记录并在其发生任何变化时更新记录是合乎逻辑的,但是更新操作对 DBMS 来说是昂贵且缓慢的,因为它需要重写存储中的数据。如果你需要快速的写入数据,则更新操作是不可接受的,但是你可以按下面的描述顺序地更新一个对象的变化。
在写入行的时候使用特定的列 Sign
。如果 Sign = 1
则表示这一行是对象的状态,我们称之为«状态»行。如果 Sign = -1
则表示是对具有相同属性的状态行的取消,我们称之为«取消»行。
例如,我们想要计算用户在某个站点访问的页面页面数以及他们在那里停留的时间。在某个时候,我们将用户的活动状态写入下面这样的行。
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
一段时间后,我们写入下面的两行来记录用户活动的变化。
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
第一行取消了这个对象(用户)的状态。它需要复制被取消的状态行的所有除了 Sign
的属性。
第二行包含了当前的状态。
因为我们只需要用户活动的最后状态,这些行
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
│ 4324182021466249494 │ 5 │ 146 │ -1 │
└─────────────────────┴───────────┴──────────┴──────┘
可以在折叠对象的失效(老的)状态的时候被删除。CollapsingMergeTree
会在合并数据片段的时候做这件事。
为什么我们每次改变需要 2 行可以阅读算法段。
这种方法的特殊属性
- 写入的程序应该记住对象的状态从而可以取消它。«取消»字符串应该是«状态»字符串的复制,除了相反的
Sign
。它增加了存储的初始数据的大小,但使得写入数据更快速。 - 由于写入的负载,列中长的增长阵列会降低引擎的效率。数据越简单,效率越高。
SELECT
的结果很大程度取决于对象变更历史的一致性。在准备插入数据时要准确。在不一致的数据中会得到不可预料的结果,例如,像会话深度这种非负指标的负值。
算法
当 ClickHouse 合并数据片段时,每组具有相同主键的连续行被减少到不超过两行,一行 Sign = 1
(«状态»行),另一行 Sign = -1
(«取消»行),换句话说,数据项被折叠了。
对每个结果的数据部分 ClickHouse 保存:
1. 第一个«取消»和最后一个«状态»行,如果«状态»和«取消»行的数量匹配和最后一个行是«状态»行
2. 最后一个«状态»行,如果«状态»行比«取消»行多一个或一个以上。
3. 第一个«取消»行,如果«取消»行比«状态»行多一个或一个以上。
4. 没有行,在其他所有情况下。
合并会继续,但是 ClickHouse 会把此情况视为逻辑错误并将其记录在服务日志中。这个错误会在相同的数据被插入超过一次时出现。
因此,折叠不应该改变统计数据的结果。 变化逐渐地被折叠,因此最终几乎每个对象都只剩下了最后的状态。
Sign
是必须的因为合并算法不保证所有有相同主键的行都会在同一个结果数据片段中,甚至是在同一台物理服务器上。ClickHouse 用多线程来处理 SELECT
请求,所以它不能预测结果中行的顺序。如果要从 CollapsingMergeTree
表中获取完全«折叠»后的数据,则需要聚合。
要完成折叠,请使用 GROUP BY
子句和用于处理符号的聚合函数编写请求。例如,要计算数量,使用 sum(Sign)
而不是 count()
。要计算某物的总和,使用 sum(Sign * x)
而不是 sum(x)
,并添加 HAVING sum(Sign) > 0
子句。
聚合体 count
,sum
和 avg
可以用这种方式计算。如果一个对象至少有一个未被折叠的状态,则可以计算 uniq
聚合。min
和 max
聚合无法计算,因为 CollaspingMergeTree
不会保存折叠状态的值的历史记录。
如果你需要在不进行聚合的情况下获取数据(例如,要检查是否存在最新值与特定条件匹配的行),你可以在 FROM
从句中使用 FINAL
修饰符。这种方法显然是更低效的。
示例
示例数据:
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
建表:
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)
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1),(4324182021466249494, 6, 185, 1)
我们使用两次 INSERT
请求来创建两个不同的数据片段。如果我们使用一个请求插入数据,ClickHouse 只会创建一个数据片段且不会执行任何合并操作。
获取数据:
SELECT * FROM UAct
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 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
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │ 6 │ 185 │
└─────────────────────┴───────────┴──────────┘
如果我们不需要聚合并想要强制进行折叠,我们可以在 FROM
从句中使用 FINAL
修饰语。
SELECT * FROM UAct FINAL
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
这种查询数据的方法是非常低效的。不要在大表中使用它。
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, ...]
有关建表参数的描述,可参考 创建表。
ReplacingMergeTree 的参数
-
ver
— 版本列。类型为UInt*
,Date
或DateTime
。可选参数。在数据合并的时候,
ReplacingMergeTree
从所有具有相同排序键的行中选择一行留下:- 如果
ver
列未指定,保留最后一条。 - 如果
ver
列已指定,保留ver
值最大的版本。
- 如果
子句
创建 ReplacingMergeTree
表时,需要使用与创建 MergeTree
表时相同的 子句。
数据副本
只有 MergeTree 系列里的表可支持副本:
- ReplicatedMergeTree
- ReplicatedSummingMergeTree
- ReplicatedReplacingMergeTree
- ReplicatedAggregatingMergeTree
- ReplicatedCollapsingMergeTree
- ReplicatedVersionedCollapsingMergeTree
- ReplicatedGraphiteMergeTree
副本是表级别的,不是整个服务器级的。所以,服务器里可以同时有复制表和非复制表。
副本不依赖分片。每个分片有它自己的独立副本。
对于 INSERT
和 ALTER
语句操作数据的会在压缩的情况下被复制(更多信息,看 ALTER )。
而 CREATE
,DROP
,ATTACH
,DETACH
和 RENAME
语句只会在单个服务器上执行,不会被复制。
The CREATE TABLE
在运行此语句的服务器上创建一个新的可复制表。如果此表已存在其他服务器上,则给该表添加新副本。The DROP TABLE
删除运行此查询的服务器上的副本。The RENAME
重命名一个副本。换句话说,可复制表不同的副本可以有不同的名称。
要使用副本,需在配置文件中设置 ZooKeeper 集群的地址。例如:
<zookeeper>
<node index="1">
<host>example1</host>
<port>2181</port>
</node>
<node index="2">
<host>example2</host>
<port>2181</port>
</node>
<node index="3">
<host>example3</host>
<port>2181</port>
</node>
</zookeeper>
需要 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)
已弃用的建表语法示例:
CREATE TABLE table_name
(
EventDate DateTime,
CounterID UInt32,
UserID UInt32
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/table_name', '{replica}', EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID), EventTime), 8192)
如上例所示,这些参数可以包含宏替换的占位符,即大括号的部分。它们会被替换为配置文件里 ‘macros’ 那部分配置的值。示例:
<macros>
<layer>05</layer>
<shard>02</shard>
<replica>example05-02-1.yandex.ru</replica>
</macros>
«ZooKeeper 中该表的路径»对每个可复制表都要是唯一的。不同分片上的表要有不同的路径。 这种情况下,路径包含下面这些部分:
/clickhouse/tables/
是公共前缀,我们推荐使用这个。
{layer}-{shard}
是分片标识部分。在此示例中,由于 Yandex.Metrica 集群使用了两级分片,所以它是由两部分组成的。但对于大多数情况来说,你只需保留 {shard} 占位符即可,它会替换展开为分片标识。
table_name
是该表在 ZooKeeper 中的名称。使其与 ClickHouse 中的表名相同比较好。 这里它被明确定义,跟 ClickHouse 表名不一样,它并不会被 RENAME 语句修改。 HINT:你可以在前面添加一个数据库名称 table_name
也是 例如。 db_name.table_name
副本名称用于标识同一个表分片的不同副本。你可以使用服务器名称,如上例所示。同个分片中不同副本的副本名称要唯一。
你也可以显式指定这些参数,而不是使用宏替换。对于测试和配置小型集群这可能会很方便。但是,这种情况下,则不能使用分布式 DDL 语句(ON CLUSTER
)。
使用大型集群时,我们建议使用宏替换,因为它可以降低出错的可能性。
在每个副本服务器上运行 CREATE TABLE
查询。将创建新的复制表,或给现有表添加新副本。
如果其他副本上已包含了某些数据,在表上添加新副本,则在运行语句后,数据会从其他副本复制到新副本。换句话说,新副本会与其他副本同步。
要删除副本,使用 DROP TABLE
。但它只删除那个 – 位于运行该语句的服务器上的副本。
故障恢复
如果服务器启动时 ZooKeeper 不可用,则复制表会切换为只读模式。系统会定期尝试去连接 ZooKeeper。
如果在 INSERT
期间 ZooKeeper 不可用,或者在与 ZooKeeper 交互时发生错误,则抛出异常。
连接到 ZooKeeper 后,系统会检查本地文件系统中的数据集是否与预期的数据集( ZooKeeper 存储此信息)一致。如果存在轻微的不一致,系统会通过与副本同步数据来解决。
如果系统检测到损坏的数据片段(文件大小错误)或无法识别的片段(写入文件系统但未记录在 ZooKeeper 中的部分),则会把它们移动到 ‘detached’ 子目录(不会删除)。而副本中其他任何缺少的但正常数据片段都会被复制同步。
注意,ClickHouse 不会执行任何破坏性操作,例如自动删除大量数据。
当服务器启动(或与 ZooKeeper 建立新会话)时,它只检查所有文件的数量和大小。 如果文件大小一致但中间某处已有字节被修改过,不会立即被检测到,只有在尝试读取 SELECT
查询的数据时才会检测到。该查询会引发校验和不匹配或压缩块大小不一致的异常。这种情况下,数据片段会添加到验证队列中,并在必要时从其他副本中复制。
如果本地数据集与预期数据的差异太大,则会触发安全机制。服务器在日志中记录此内容并拒绝启动。这种情况很可能是配置错误,例如,一个分片上的副本意外配置为别的分片上的副本。然而,此机制的阈值设置得相当低,在正常故障恢复期间可能会出现这种情况。在这种情况下,数据恢复则是半自动模式,通过用户主动操作触发。
要触发启动恢复,可在 ZooKeeper 中创建节点 /path_to_table/replica_name/flags/force_restore_data
,节点值可以是任何内容,或运行命令来恢复所有的可复制表:
sudo -u clickhouse touch /var/lib/clickhouse/flags/force_restore_data
然后重启服务器。启动时,服务器会删除这些标志并开始恢复。
在数据完全丢失后的恢复
如果其中一个服务器的所有数据和元数据都消失了,请按照以下步骤进行恢复:
- 在服务器上安装 ClickHouse。在包含分片标识符和副本的配置文件中正确定义宏配置,如果有用到的话,
- 如果服务器上有非复制表则必须手动复制,可以从副本服务器上(在
/var/lib/clickhouse/data/db_name/table_name/
目录中)复制它们的数据。 - 从副本服务器上中复制位于
/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 的参数
-
columns
- 包含了将要被汇总的列的列名的元组。可选参数。 所选的列必须是数值类型,并且不可位于主键中。如果没有指定 `columns`,ClickHouse 会把所有不在主键中的数值类型的列都进行汇总。
子句
创建 SummingMergeTree
表时,需要与创建 MergeTree
表时相同的子句。
已弃用的建表方法
用法示例
考虑如下的表:
CREATE TABLE summtt
(
key UInt32,
value UInt32
)
ENGINE = SummingMergeTree()
ORDER BY key
向其中插入数据:
:) INSERT INTO summtt Values(1,1),(1,2),(2,1)
ClickHouse可能不会完整的汇总所有行(见下文),因此我们在查询中使用了聚合函数 sum
和 GROUP BY
子句。
SELECT key, sum(value) FROM summtt GROUP BY key
┌─key─┬─sum(value)─┐
│ 2 │ 1 │
│ 1 │ 3 │
└─────┴────────────┘
数据处理
当数据被插入到表中时,他们将被原样保存。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)]
请求数据时,使用 sumMap(key,value) 函数来对 Map
进行聚合。
对于嵌套数据结构,你无需在列的元组中指定列以进行汇总。
日志引擎系列
这些引擎是为了需要写入许多小数据量(少于一百万行)的表的场景而开发的。
这系列的引擎有:
共同属性
引擎:
-
数据存储在磁盘上。
-
写入时将数据追加在文件末尾。
-
不支持突变操作。
-
不支持索引。
这意味着 `SELECT` 在范围查询时效率不高。
-
非原子地写入数据。
如果某些事情破坏了写操作,例如服务器的异常关闭,你将会得到一张包含了损坏数据的表。
差异
Log
和 StripeLog
引擎支持:
-
并发访问数据的锁。
`INSERT` 请求执行过程中表会被锁定,并且其他的读写数据的请求都会等待直到锁定被解除。如果没有写数据的请求,任意数量的读请求都可以并发执行。
-
并行读取数据。
在读取数据时,ClickHouse 使用多线程。 每个线程处理不同的数据块。
Log
引擎为表中的每一列使用不同的文件。StripeLog
将所有的数据存储在一个文件中。因此 StripeLog
引擎在操作系统中使用更少的描述符,但是 Log
引擎提供更高的读性能。
TinyLog
引擎是该系列中最简单的引擎并且提供了最少的功能和最低的性能。TinyLog
引擎不支持并行读取和并发数据访问,并将每一列存储在不同的文件中。它比其余两种支持并行读取的引擎的读取速度更慢,并且使用了和 Log
引擎同样多的描述符。你可以在简单的低负载的情景下使用它。
Log
Log
与 TinyLog
的不同之处在于,«标记» 的小文件与列文件存在一起。这些标记写在每个数据块上,并且包含偏移量,这些偏移量指示从哪里开始读取文件以便跳过指定的行数。这使得可以在多个线程中读取表数据。对于并发数据访问,可以同时执行读取操作,而写入操作则阻塞读取和其它写入。Log
引擎不支持索引。同样,如果写入表失败,则该表将被破坏,并且从该表读取将返回错误。Log
引擎适用于临时数据,write-once 表以及测试或演示目的。
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 │
└─────────────────────┴──────────────┴────────────────────────────┘
TinyLog
最简单的表引擎,用于将数据存储在磁盘上。每列都存储在单独的压缩文件中。写入时,数据将附加到文件末尾。
并发数据访问不受任何限制:
- 如果同时从表中读取并在不同的查询中写入,则读取操作将抛出异常
- 如果同时写入多个查询中的表,则数据将被破坏。
这种表引擎的典型用法是 write-once:首先只写入一次数据,然后根据需要多次读取。查询在单个流中执行。换句话说,此引擎适用于相对较小的表(建议最多1,000,000行)。如果您有许多小表,则使用此表引擎是适合的,因为它比Log引擎更简单(需要打开的文件更少)。当您拥有大量小表时,可能会导致性能低下,但在可能已经在其它 DBMS 时使用过,则您可能会发现切换使用 TinyLog 类型的表更容易。不支持索引。
在 Yandex.Metrica 中,TinyLog 表用于小批量处理的中间数据。
集成的表引擎
ClickHouse 提供了多种方式来与外部系统集成,包括表引擎。像所有其他的表引擎一样,使用CREATE TABLE
或ALTER TABLE
查询语句来完成配置。然后从用户的角度来看,配置的集成看起来像查询一个正常的表,但对它的查询是代理给外部系统的。这种透明的查询是这种方法相对于其他集成方法的主要优势之一,比如外部字典或表函数,它们需要在每次使用时使用自定义查询方法。
以下是支持的集成方式:
Hive
Hive引擎允许对HDFS Hive表执行 SELECT
查询。目前它支持如下输入格式:
-文本:只支持简单的标量列类型,除了 Binary
-
ORC:支持简单的标量列类型,除了
char
; 只支持array
这样的复杂类型 -
Parquet:支持所有简单标量列类型;只支持
array
这样的复杂类型
创建表
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [ALIAS expr1],
name2 [type2] [ALIAS expr2],
...
) ENGINE = Hive('thrift://host:port', 'database', 'table');
PARTITION BY expr
查看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>true</enable>
<root_dir>local_cache</root_dir>
<limit_size>559096952</limit_size>
<bytes_read_before_flush>1048576</bytes_read_before_flush>
</local_cache_for_remote_fs>
- enable: 开启后,ClickHouse将为HDFS (远程文件系统)维护本地缓存。
- root_dir: 必需的。用于存储远程文件系统的本地缓存文件的根目录。
- limit_size: 必需的。本地缓存文件的最大大小(单位为字节)。
- bytes_read_before_flush: 从远程文件系统下载文件时,刷新到本地文件系统前的控制字节数。缺省值为1MB。
当ClickHouse为远程文件系统启用了本地缓存时,用户仍然可以选择不使用缓存,并在查询中设置use_local_cache_for_remote_fs = 0
, use_local_cache_for_remote_fs
默认为 false
。
查询 ORC 输入格式的Hive 表
在 Hive 中建表
hive > CREATE TABLE `test`.`test_orc`(
`f_tinyint` tinyint,
`f_smallint` smallint,
`f_int` int,
`f_integer` int,
`f_bigint` bigint,
`f_float` float,
`f_double` double,
`f_decimal` decimal(10,0),
`f_timestamp` timestamp,
`f_date` date,
`f_string` string,
`f_varchar` varchar(100),
`f_bool` boolean,
`f_binary` binary,
`f_array_int` array<int>,
`f_array_string` array<string>,
`f_array_float` array<float>,
`f_array_array_int` array<array<int>>,
`f_array_array_string` array<array<string>>,
`f_array_array_float` array<array<float>>)
PARTITIONED BY (
`day` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.orc.OrcSerde'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat'
LOCATION
'hdfs://testcluster/data/hive/test.db/test_orc'
OK
Time taken: 0.51 seconds
hive > insert into test.test_orc partition(day='2021-09-18') select 1, 2, 3, 4, 5, 6.11, 7.22, 8.333, current_timestamp(), current_date(), 'hello world', 'hello world', 'hello world', true, 'hello world', array(1, 2, 3), array('hello world', 'hello world'), array(float(1.1), float(1.2)), array(array(1, 2), array(3, 4)), array(array('a', 'b'), array('c', 'd')), array(array(float(1.11), float(2.22)), array(float(3.33), float(4.44)));
OK
Time taken: 36.025 seconds
hive > select * from test.test_orc;
OK
1 2 3 4 5 6.11 7.22 8 2021-11-05 12:38:16.314 2021-11-05 hello world hello world hello world true hello world [1,2,3] ["hello world","hello world"] [1.1,1.2] [[1,2],[3,4]] [["a","b"],["c","d"]] [[1.11,2.22],[3.33,4.44]] 2021-09-18
Time taken: 0.295 seconds, Fetched: 1 row(s)
在 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\G
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.
查询 Parquest 输入格式的Hive 表
在 Hive 中建表
hive >
CREATE TABLE `test`.`test_parquet`(
`f_tinyint` tinyint,
`f_smallint` smallint,
`f_int` int,
`f_integer` int,
`f_bigint` bigint,
`f_float` float,
`f_double` double,
`f_decimal` decimal(10,0),
`f_timestamp` timestamp,
`f_date` date,
`f_string` string,
`f_varchar` varchar(100),
`f_char` char(100),
`f_bool` boolean,
`f_binary` binary,
`f_array_int` array<int>,
`f_array_string` array<string>,
`f_array_float` array<float>,
`f_array_array_int` array<array<int>>,
`f_array_array_string` array<array<string>>,
`f_array_array_float` array<array<float>>)
PARTITIONED BY (
`day` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
'hdfs://testcluster/data/hive/test.db/test_parquet'
OK
Time taken: 0.51 seconds
hive > insert into test.test_parquet partition(day='2021-09-18') select 1, 2, 3, 4, 5, 6.11, 7.22, 8.333, current_timestamp(), current_date(), 'hello world', 'hello world', 'hello world', true, 'hello world', array(1, 2, 3), array('hello world', 'hello world'), array(float(1.1), float(1.2)), array(array(1, 2), array(3, 4)), array(array('a', 'b'), array('c', 'd')), array(array(float(1.11), float(2.22)), array(float(3.33), float(4.44)));
OK
Time taken: 36.025 seconds
hive > select * from test.test_parquet;
OK
1 2 3 4 5 6.11 7.22 8 2021-12-14 17:54:56.743 2021-12-14 hello world hello world hello world true hello world [1,2,3] ["hello world","hello world"] [1.1,1.2] [[1,2],[3,4]] [["a","b"],["c","d"]] [[1.11,2.22],[3.33,4.44]] 2021-09-18
Time taken: 0.766 seconds, Fetched: 1 row(s)
在 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.test_parquet settings input_format_parquet_allow_missing_columns = 1\G
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.
查询文本输入格式的Hive表
在Hive 中建表
hive >
CREATE TABLE `test`.`test_text`(
`f_tinyint` tinyint,
`f_smallint` smallint,
`f_int` int,
`f_integer` int,
`f_bigint` bigint,
`f_float` float,
`f_double` double,
`f_decimal` decimal(10,0),
`f_timestamp` timestamp,
`f_date` date,
`f_string` string,
`f_varchar` varchar(100),
`f_char` char(100),
`f_bool` boolean,
`f_binary` binary,
`f_array_int` array<int>,
`f_array_string` array<string>,
`f_array_float` array<float>,
`f_array_array_int` array<array<int>>,
`f_array_array_string` array<array<string>>,
`f_array_array_float` array<array<float>>)
PARTITIONED BY (
`day` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
'hdfs://testcluster/data/hive/test.db/test_text'
Time taken: 0.1 seconds, Fetched: 34 row(s)
hive > insert into test.test_text partition(day='2021-09-18') select 1, 2, 3, 4, 5, 6.11, 7.22, 8.333, current_timestamp(), current_date(), 'hello world', 'hello world', 'hello world', true, 'hello world', array(1, 2, 3), array('hello world', 'hello world'), array(float(1.1), float(1.2)), array(array(1, 2), array(3, 4)), array(array('a', 'b'), array('c', 'd')), array(array(float(1.11), float(2.22)), array(float(3.33), float(4.44)));
OK
Time taken: 36.025 seconds
hive > select * from test.test_text;
OK
1 2 3 4 5 6.11 7.22 8 2021-12-14 18:11:17.239 2021-12-14 hello world hello world hello world true hello world [1,2,3] ["hello world","hello world"] [1.1,1.2] [[1,2],[3,4]] [["a","b"],["c","d"]] [[1.11,2.22],[3.33,4.44]] 2021-09-18
Time taken: 0.624 seconds, Fetched: 1 row(s)
在 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'\G
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);
引擎参数
-
host:port
— MongoDB 服务器地址. -
database
— 数据库名称. -
collection
— 集合名称. -
user
— MongoDB 用户. -
password
— 用户密码.
用法示例
ClickHouse 中的表,从 MongoDB 集合中读取数据:
CREATE TABLE mongo_table
(
key UInt64,
data String
) ENGINE = MongoDB('mongo1:27017', 'test', 'simple_table', 'testuser', 'clickhouse');
查询:
SELECT COUNT() FROM mongo_table;
┌─count()─┐
│ 4 │
SQLite
该引擎允许导入和导出数据到SQLite,并支持查询SQLite表直接从ClickHouse。
创建数据表
CREATE TABLE [IF NOT EXISTS] [db.]table_name
(
name1 [type1],
name2 [type2], ...
) ENGINE = SQLite('db_path', 'table')
引擎参数
db_path
— SQLite数据库文件的具体路径地址。table
— SQLite数据库中的表名。
使用示例
显示创建表的查询语句:
SHOW CREATE TABLE sqlite_db.table2;
CREATE TABLE SQLite.table2
(
`col1` Nullable(Int32),
`col2` Nullable(String)
)
ENGINE = SQLite('sqlite.db','table2');
从数据表查询数据:
SELECT * FROM sqlite_db.table2 ORDER BY col1;
┌─col1─┬─col2──┐
│ 1 │ text1 │
│ 2 │ text2 │
│ 3 │ text3 │
└──────┴───────┘
详见
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)
必要参数:
primary_key_name
– any column name in the column list.- 必须指定
primary key
, 仅支持主键中的一个列. 主键将被序列化为二进制的rocksdb key
. - 主键以外的列将以相应的顺序在二进制中序列化为
rocksdb
值. - 带有键
equals
或in
过滤的查询将被优化为从rocksdb
进行多键查询.
示例:
CREATE TABLE test
(
`key` String,
`v1` UInt32,
`v2` String,
`v3` Float32,
)
ENGINE = EmbeddedRocksDB
PRIMARY KEY key
指标
还有一个system.rocksdb
表, 公开rocksdb的统计信息:
SELECT
name,
value
FROM system.rocksdb
┌─name──────────────────────┬─value─┐
│ no.file.opens │ 1 │
│ number.block.decompressed │ 1 │
└───────────────────────────┴───────┘
配置
你能修改任何rocksdb options 配置,使用配置文件:
<rocksdb>
<options>
<max_background_jobs>8</max_background_jobs>
</options>
<column_family_options>
<num_levels>2</num_levels>
</column_family_options>
<tables>
<table>
<name>TABLE</name>
<options>
<max_background_jobs>8</max_background_jobs>
</options>
<column_family_options>
<num_levels>2</num_levels>
</column_family_options>
</table>
</tables>
</rocksdb>
RabbitMQ 引擎
该引擎允许 ClickHouse 与 RabbitMQ 进行集成.
RabbitMQ
可以让你:
- 发布或订阅数据流。
- 在数据流可用时进行处理。
创建一张表
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = RabbitMQ SETTINGS
rabbitmq_host_port = 'host:port',
rabbitmq_exchange_name = 'exchange_name',
rabbitmq_format = 'data_format'[,]
[rabbitmq_exchange_type = 'exchange_type',]
[rabbitmq_routing_key_list = 'key1,key2,...',]
[rabbitmq_row_delimiter = 'delimiter_symbol',]
[rabbitmq_schema = '',]
[rabbitmq_num_consumers = N,]
[rabbitmq_num_queues = N,]
[rabbitmq_queue_base = 'queue',]
[rabbitmq_deadletter_exchange = 'dl-exchange',]
[rabbitmq_persistent = 0,]
[rabbitmq_skip_broken_messages = N,]
[rabbitmq_max_block_size = N,]
[rabbitmq_flush_interval_ms = N]
必要参数:
rabbitmq_host_port
– 主机名:端口号 (比如,localhost:5672
).rabbitmq_exchange_name
– RabbitMQ exchange 名称.rabbitmq_format
– 消息格式. 使用与SQLFORMAT
函数相同的标记,如JSONEachRow
。 更多信息,请参阅 Formats 部分.
可选参数:
rabbitmq_exchange_type
– RabbitMQ exchange 的类型:direct
,fanout
,topic
,headers
,consistent_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 服务器配置应使用 ClickHouse 配置文件添加。
必要配置:
<rabbitmq>
<username>root</username>
<password>clickhouse</password>
</rabbitmq>
可选配置:
<rabbitmq>
<vhost>clickhouse</vhost>
</rabbitmq>
描述
SELECT
对于读取消息不是特别有用(除了调试),因为每个消息只能读取一次。使用物化视图创建实时线程更为实用。要做到这一点:
- 使用引擎创建一个 RabbitMQ 消费者,并将其视为一个数据流。
- 创建一个具有所需结构的表。
- 创建一个物化视图,转换来自引擎的数据并将其放入先前创建的表中。
当物化视图
加入引擎时,它开始在后台收集数据。这允许您持续接收来自 RabbitMQ 的消息,并使用 SELECT
将它们转换为所需格式。 一个 RabbitMQ 表可以有多个你需要的物化视图。
数据可以根据rabbitmq_exchange_type
和指定的rabbitmq_routing_key_list
进行通道。 每个表不能有多于一个 exchange。一个 exchange 可以在多个表之间共享 - 因为可以使用路由让数据同时进入多个表。
Exchange 类型的选项:
direct
- 路由是基于精确匹配的键。例如表的键列表:key1,key2,key3,key4,key5
, 消息键可以是等同他们中的任意一个.fanout
- 路由到所有的表 (exchange 名称相同的情况) 无论是什么键都是这样.topic
- 路由是基于带有点分隔键的模式. 比如:*.logs
,records.*.*.2020
,*.2018,*.2019,*.2020
.headers
- 路由是基于key=value
的匹配,设置为x-match=all
或x-match=any
. 例如表的键列表:x-match=all,format=logs,type=report,year=2020
.consistent_hash
- 数据在所有绑定的表之间均匀分布 (exchange 名称相同的情况). 请注意,这种 exchange 类型必须启用 RabbitMQ 插件:rabbitmq-plugins enable rabbitmq_consistent_hash_exchange
.
设置rabbitmq_queue_base
可用于以下情况:
- 来让不同的表共享队列, 这样就可以为同一个队列注册多个消费者,这使得性能更好。如果使用
rabbitmq_num_consumers
和/或rabbitmq_num_queues
设置,在这些参数相同的情况下,实现队列的精确匹配。 - 以便在不是所有消息都被成功消费时,能够恢复从某些持久队列的阅读。要从一个特定的队列恢复消耗 - 在
rabbitmq_queue_base
设置中设置其名称,不要指定rabbitmq_num_consumers
和rabbitmq_num_queues
(默认为1)。要恢复所有队列的消费,这些队列是为一个特定的表所声明的 - 只要指定相同的设置。rabbitmq_queue_base
,rabbitmq_num_consumers
,rabbitmq_num_queues
。默认情况下,队列名称对表来说是唯一的。 - 以重复使用队列,因为它们被声明为持久的,并且不会自动删除。可以通过任何 RabbitMQ CLI 工具删除)
为了提高性能,收到的消息被分组为大小为 max_insert_block_size 的块。如果在stream_flush_interval_ms毫秒内没有形成数据块,无论数据块是否完整,数据都会被刷到表中。
如果rabbitmq_num_consumers
和/或rabbitmq_num_queues
设置与rabbitmq_exchange_type
一起被指定,那么:
- 必须启用
rabbitmq-consistent-hash-exchange
插件. - 必须指定已发布信息的
message_id
属性(对于每个信息/批次都是唯一的)。
对于插入查询时有消息元数据,消息元数据被添加到每个发布的消息中:messageID
和republished
标志(如果值为true,则表示消息发布不止一次) - 可以通过消息头访问。
不要在插入和物化视图中使用同一个表。
示例:
CREATE TABLE queue (
key UInt64,
value UInt64
) ENGINE = RabbitMQ SETTINGS rabbitmq_host_port = 'localhost:5672',
rabbitmq_exchange_name = 'exchange1',
rabbitmq_exchange_type = 'headers',
rabbitmq_routing_key_list = 'format=logs,type=report,year=2020',
rabbitmq_format = 'JSONEachRow',
rabbitmq_num_consumers = 5;
CREATE TABLE daily (key UInt64, value UInt64)
ENGINE = MergeTree() ORDER BY key;
CREATE MATERIALIZED VIEW consumer TO daily
AS SELECT key, value FROM queue;
SELECT key, value FROM daily ORDER BY key;
虚拟列
_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 源表结构不同:
- 列名应与 PostgreSQL 源表中的列名相同,但您可以按任何顺序使用其中的一些列。
- 列类型可能与源表中的列类型不同。 ClickHouse尝试将数值映射 到ClickHouse的数据类型。
- 设置
external_table_functions_use_nulls
来定义如何处理 Nullable 列. 默认值是 1, 当设置为 0 时 - 表函数将不会使用 nullable 列,而是插入默认值来代替 null. 这同样适用于数组数据类型中的 null 值.
引擎参数
host:port
— PostgreSQL 服务器地址.database
— 数据库名称.table
— 表名称.user
— PostgreSQL 用户.password
— 用户密码.schema
— Non-default table schema. 可选.
实施细节
在 PostgreSQL 上的 SELECT
查询以 COPY (SELECT ...) TO STDOUT
的方式在只读 PostgreSQL 事务中运行,每次 SELECT
查询后提交。
简单的 WHERE
子句,如=
,!=
,>
,>=
,<
,<=
,和IN
是在PostgreSQL 服务器上执行。
所有的连接、聚合、排序、IN [ array ]
条件和LIMIT
采样约束都是在 PostgreSQL 的查询结束后才在ClickHouse中执行的。
在 PostgreSQL 上的 INSERT
查询以 COPY "table_name" (field1, field2, ... fieldN) FROM STDIN
的方式在 PostgreSQL 事务中运行,每条 INSERT
语句后自动提交。
PostgreSQL 的 Array
类型会被转换为 ClickHouse 数组。
!!! info "Note" 要小心 - 一个在 PostgreSQL 中的数组数据,像type_name[]
这样创建,可以在同一列的不同表行中包含不同维度的多维数组。但是在 ClickHouse 中,只允许在同一列的所有表行中包含相同维数的多维数组。
支持设置 PostgreSQL 字典源中 Replicas 的优先级。地图中的数字越大,优先级就越低。最高的优先级是 0
。
在下面的例子中,副本example01-1
有最高的优先级。
<postgresql>
<port>5432</port>
<user>clickhouse</user>
<password>qwerty</password>
<replica>
<host>example01-1</host>
<priority>1</priority>
</replica>
<replica>
<host>example01-2</host>
<priority>2</priority>
</replica>
<db>db_name</db>
<table>table_name</table>
<where>id=10</where>
<invalidate_query>SQL_QUERY</invalidate_query>
</postgresql>
</source>
用法示例
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)
ClickHouse 中的表, 从上面创建的 PostgreSQL 表中检索数据:
CREATE TABLE default.postgresql_table
(
`float_nullable` Nullable(Float32),
`str` String,
`int_id` Int32
)
ENGINE = PostgreSQL('localhost:5432', 'public', 'test', 'postges_user', 'postgres_password');
SELECT * FROM postgresql_table WHERE str IN ('test');
┌─float_nullable─┬─str──┬─int_id─┐
│ ᴺᵁᴸᴸ │ test │ 1 │
└────────────────┴──────┴────────┘
使用非默认的模式:
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)
CREATE TABLE pg_table_schema_with_dots (a UInt32)
ENGINE PostgreSQL('localhost:5432', 'clickhouse', 'nice.table', 'postgrsql_user', 'password', 'nice.schema');
JDBC
允许CH通过 JDBC 连接到外部数据库。
要实现JDBC连接,CH需要使用以后台进程运行的程序 clickhouse-jdbc-bridge。
该引擎支持 Nullable 数据类型。
建表
CREATE TABLE [IF NOT EXISTS] [db.]table_name
(
columns list...
)
ENGINE = JDBC(datasource_uri, external_database, external_table)
引擎参数
-
datasource_uri
— 外部DBMS的URI或名字.URI格式:
jdbc:<driver_name>://<host_name>:<port>/?user=<username>&password=<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)
在CH服务端创建表,并从中查询数据:
CREATE TABLE jdbc_table
(
`int_id` Int32,
`int_nullable` Nullable(Int32),
`float` Float32,
`float_nullable` Nullable(Float32)
)
ENGINE JDBC('jdbc:mysql://localhost:3306/?user=root&password=root', 'test', 'test')
SELECT *
FROM jdbc_table
┌─int_id─┬─int_nullable─┬─float─┬─float_nullable─┐
│ 1 │ ᴺᵁᴸᴸ │ 2 │ ᴺᵁᴸᴸ │
└────────┴──────────────┴───────┴────────────────┘
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)
详情请见 CREATE TABLE 查询。
表结构可以与源表结构不同:
- 列名应与源表中的列名相同,但您可以按任何顺序使用其中的一些列。
- 列类型可能与源表中的列类型不同。 ClickHouse尝试将数值映射 到ClickHouse的数据类型。
- 设置
external_table_functions_use_nulls
来定义如何处理 Nullable 列. 默认值是 true, 当设置为 false 时 - 表函数将不会使用 nullable 列,而是插入默认值来代替 null. 这同样适用于数组数据类型中的 null 值.
引擎参数
connection_settings
— 在odbc.ini
配置文件中,连接配置的名称.external_database
— 在外部 DBMS 中的数据库名.external_table
—external_database
中的表名.
用法示例
通过ODBC从本地安装的MySQL中检索数据
本示例已经在 Ubuntu Linux 18.04 和 MySQL server 5.7 上测试通过。
请确保已经安装了 unixODBC 和 MySQL Connector。
默认情况下(如果从软件包安装),ClickHouse以用户clickhouse
的身份启动. 因此,您需要在MySQL服务器中创建并配置此用户。
$ sudo mysql
mysql> CREATE USER 'clickhouse'@'localhost' IDENTIFIED BY 'clickhouse';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'clickhouse'@'clickhouse' WITH GRANT OPTION;
然后在/etc/odbc.ini
中配置连接.
$ cat /etc/odbc.ini
[mysqlconn]
DRIVER = /usr/local/lib/libmyodbc5w.so
SERVER = 127.0.0.1
PORT = 3306
DATABASE = test
USERNAME = clickhouse
PASSWORD = clickhouse
您可以从安装的 unixodbc 中使用 isql
实用程序来检查连接情况。
$ isql -v mysqlconn
+---------------------------------------+
| Connected! |
| |
...
MySQL中的表:
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)
ClickHouse中的表,从MySQL表中检索数据:
CREATE TABLE odbc_t
(
`int_id` Int32,
`float_nullable` Nullable(Float32)
)
ENGINE = ODBC('DSN=mysqlconn', 'test', 'test')
SELECT * FROM odbc_t
┌─int_id─┬─float_nullable─┐
│ 1 │ ᴺᵁᴸᴸ │
└────────┴────────────────┘
Kafka
此引擎与 Apache Kafka 结合使用。
Kafka 特性:
- 发布或者订阅数据流。
- 容错存储机制。
- 处理流数据。
老版格式:
Kafka(kafka_broker_list, kafka_topic_list, kafka_group_name, kafka_format
[, kafka_row_delimiter, kafka_schema, kafka_num_consumers])
新版格式:
Kafka SETTINGS
kafka_broker_list = 'localhost:9092',
kafka_topic_list = 'topic1,topic2',
kafka_group_name = 'group1',
kafka_format = 'JSONEachRow',
kafka_row_delimiter = '\n',
kafka_schema = '',
kafka_num_consumers = 2
必要参数:
kafka_broker_list
– 以逗号分隔的 brokers 列表 (localhost:9092
)。kafka_topic_list
– topic 列表 (my_topic
)。kafka_group_name
– Kafka 消费组名称 (group1
)。如果不希望消息在集群中重复,请在每个分片中使用相同的组名。kafka_format
– 消息体格式。使用与 SQL 部分的FORMAT
函数相同表示方法,例如JSONEachRow
。了解详细信息,请参考Formats
部分。
可选参数:
kafka_row_delimiter
- 每个消息体(记录)之间的分隔符。kafka_schema
– 如果解析格式需要一个 schema 时,此参数必填。例如,普罗托船长 需要 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;
消费的消息会被自动追踪,因此每个消息在不同的消费组里只会记录一次。如果希望获得两次数据,则使用另一个组名创建副本。
消费组可以灵活配置并且在集群之间同步。例如,如果群集中有10个主题和5个表副本,则每个副本将获得2个主题。 如果副本数量发生变化,主题将自动在副本中重新分配。了解更多信息请访问 http://kafka.apache.org/intro。
SELECT
查询对于读取消息并不是很有用(调试除外),因为每条消息只能被读取一次。使用物化视图创建实时线程更实用。您可以这样做:
- 使用引擎创建一个 Kafka 消费者并作为一条数据流。
- 创建一个结构表。
- 创建物化视图,改视图会在后台转换引擎中的数据并将其放入之前创建的表中。
当 MATERIALIZED VIEW
添加至引擎,它将会在后台收集数据。可以持续不断地从 Kafka 收集数据并通过 SELECT
将数据转换为所需要的格式。
示例:
CREATE TABLE queue (
timestamp UInt64,
level String,
message String
) ENGINE = Kafka('localhost:9092', 'topic', 'group1', 'JSONEachRow');
CREATE TABLE daily (
day Date,
level String,
total UInt64
) ENGINE = SummingMergeTree(day, (day, level), 8192);
CREATE MATERIALIZED VIEW consumer TO daily
AS SELECT toDate(toDateTime(timestamp)) AS day, level, count() as total
FROM queue GROUP BY day, level;
SELECT level, sum(total) FROM daily GROUP BY level;
为了提高性能,接受的消息被分组为 max_insert_block_size 大小的块。如果未在 stream_flush_interval_ms 毫秒内形成块,则不关心块的完整性,都会将数据刷新到表中。
停止接收主题数据或更改转换逻辑,请 detach 物化视图:
DETACH TABLE consumer;
ATTACH TABLE consumer;
如果使用 ALTER
更改目标表,为了避免目标表与视图中的数据之间存在差异,推荐停止物化视图。
配置
与 GraphiteMergeTree
类似,Kafka 引擎支持使用ClickHouse配置文件进行扩展配置。可以使用两个配置键:全局 (kafka
) 和 主题级别 (kafka_*
)。首先应用全局配置,然后应用主题级配置(如果存在)。
<!-- Global configuration options for all tables of Kafka engine type -->
<kafka>
<debug>cgrp</debug>
<auto_offset_reset>smallest</auto_offset_reset>
</kafka>
<!-- Configuration specific for topic "logs" -->
<kafka_logs>
<retry_backoff_ms>250</retry_backoff_ms>
<fetch_min_bytes>100000</fetch_min_bytes>
</kafka_logs>
有关详细配置选项列表,请参阅 librdkafka配置参考。在 ClickHouse 配置中使用下划线 (_
) ,并不是使用点 (.
)。例如,check.crcs=true
将是 <check_crcs>true</check_crcs>
。
Kerberos 支持
对于使用了kerberos的kafka, 将security_protocol 设置为sasl_plaintext就够了,如果kerberos的ticket是由操作系统获取和缓存的。 clickhouse也支持自己使用keyfile的方式来维护kerbros的凭证。配置sasl_kerberos_service_name、sasl_kerberos_keytab、sasl_kerberos_principal三个子元素就可以。
示例:
<!-- Kerberos-aware Kafka -->
<kafka>
<security_protocol>SASL_PLAINTEXT</security_protocol>
<sasl_kerberos_keytab>/home/kafkauser/kafkauser.keytab</sasl_kerberos_keytab>
<sasl_kerberos_principal>kafkauser/kafkahost@EXAMPLE.COM</sasl_kerberos_principal>
</kafka>
虚拟列
_topic
– Kafka 主题。_key
– 信息的键。_offset
– 消息的偏移量。_timestamp
– 消息的时间戳。_timestamp_ms
– 消息的时间戳(毫秒)。_partition
– Kafka 主题的分区。
另请参阅
MySQL
MySQL 引擎可以对存储在远程 MySQL 服务器上的数据执行 SELECT
查询。
调用格式:
MySQL('host:port', 'database', 'table', 'user', 'password'[, replace_query, 'on_duplicate_clause']);
调用参数
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, ...]
已有数据表
当 Distributed
表指向当前服务器上的一个表时,你可以采用以下语句:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] AS [db2.]name2 ENGINE = Distributed(cluster, database, table[, sharding_key[, policy_name]]) [SETTINGS name=value, ...]
分布式引擎参数
-
cluster
- 服务为配置中的集群名 -
database
- 远程数据库名 -
table
- 远程数据表名 -
sharding_key
- (可选) 分片key -
policy_name
- (可选) 规则名,它会被用作存储临时文件以便异步发送数据
详见
-
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`
示例
CREATE TABLE hits_all AS hits
ENGINE = Distributed(logs, default, hits[, sharding_key[, policy_name]])
SETTINGS
fsync_after_insert=0,
fsync_directories=0;
数据将从logs
集群中的所有服务器中,从位于集群中的每个服务器上的default.hits
表读取。。 数据不仅在远程服务器上读取,而且在远程服务器上进行部分处理(在可能的范围内)。 例如,对于带有 GROUP BY
的查询,数据将在远程服务器上聚合,聚合函数的中间状态将被发送到请求者服务器。然后将进一步聚合数据。
您可以使用一个返回字符串的常量表达式来代替数据库名称。例如: currentDatabase()
。
JOIN id_val_join USING (id) SETTINGS join_use_nulls = 1
┌─id─┬─val─┬─id_val_join.val─┐
│ 1 │ 11 │ 21 │
│ 2 │ 12 │ ᴺᵁᴸᴸ │
│ 3 │ 13 │ 23 │
└────┴─────┴─────────────────┘
作为一种替换方式,可以从 Join
表获取数据,需要设置好join的key字段值。
SELECT joinGet('id_val_join', 'val', toUInt32(1))
┌─joinGet('id_val_join', 'val', toUInt32(1))─┐
│ 21 │
└────────────────────────────────────────────┘
数据查询及插入
可以使用 INSERT
语句向 Join
引擎表中添加数据。如果表是通过指定 ANY
限制参数来创建的,那么重复key的数据会被忽略。指定 ALL
限制参数时,所有行记录都会被添加进去。
不能通过 SELECT
语句直接从表中获取数据。请使用下面的方式:
- 将表放在
JOIN
的右边进行查询 - 调用 joinGet函数,就像从字典中获取数据一样来查询表。
使用限制及参数设置
创建表时,会应用下列设置参数:
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)
生成数据时,通过max_array_length
设置array列的最大长度, max_string_length
设置string数据的最大长度
该引擎仅支持 SELECT
查询语句.
该引擎支持能在表中存储的所有数据类型 DataTypes ,除了 LowCardinality
和 AggregateFunction
.
示例
1. 设置 generate_engine_table
引擎表:
CREATE TABLE generate_engine_table (name String, value UInt32) ENGINE = GenerateRandom(1, 5, 3)
2. 查询数据:
SELECT * FROM generate_engine_table LIMIT 3
┌─name─┬──────value─┐
│ c4xJ │ 1412771199 │
│ r │ 1791099446 │
│ 7#$ │ 124312908 │
└──────┴────────────┘
实现细节
- 以下特性不支持:
ALTER
SELECT ... SAMPLE
INSERT
- Indices
- Replication
缓冲区
缓冲数据写入 RAM 中,周期性地将数据刷新到另一个表。在读取操作时,同时从缓冲区和另一个表读取数据。
Buffer(database, table, num_layers, min_time, max_time, min_rows, max_rows, min_bytes, max_bytes)
引擎的参数:database,table - 要刷新数据的表。可以使用返回字符串的常量表达式而不是数据库名称。 num_layers - 并行层数。在物理上,该表将表示为 num_layers 个独立缓冲区。建议值为16。min_time,max_time,min_rows,max_rows,min_bytes,max_bytes - 从缓冲区刷新数据的条件。
如果满足所有 «min» 条件或至少一个 «max» 条件,则从缓冲区刷新数据并将其写入目标表。min_time,max_time — 从第一次写入缓冲区时起以秒为单位的时间条件。min_rows,max_rows - 缓冲区中行数的条件。min_bytes,max_bytes - 缓冲区中字节数的条件。
写入时,数据从 num_layers 个缓冲区中随机插入。或者,如果插入数据的大小足够大(大于 max_rows 或 max_bytes ),则会绕过缓冲区将其写入目标表。
每个 «num_layers» 缓冲区刷新数据的条件是分别计算。例如,如果 num_layers = 16 且 max_bytes = 100000000,则最大RAM消耗将为1.6 GB。
示例:
CREATE TABLE merge.hits_buffer AS merge.hits ENGINE = Buffer(merge, hits, 16, 10, 100, 10000, 1000000, 10000000, 100000000)
创建一个 «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>products</name>
<source>
<odbc>
<table>products</table>
<connection_string>DSN=some-db-server</connection_string>
</odbc>
</source>
<lifetime>
<min>300</min>
<max>360</max>
</lifetime>
<layout>
<flat/>
</layout>
<structure>
<id>
<name>product_id</name>
</id>
<attribute>
<name>title</name>
<type>String</type>
<null_value></null_value>
</attribute>
</structure>
</dictionary>
</dictionaries>
查询字典中的数据:
select name, type, key, attribute.names, attribute.types, bytes_allocated, element_count,source from system.dictionaries where name = 'products';
SELECT
name,
type,
key,
attribute.names,
attribute.types,
bytes_allocated,
element_count,
source
FROM system.dictionaries
WHERE name = 'products'
┌─name─────┬─type─┬─key────┬─attribute.names─┬─attribute.types─┬─bytes_allocated─┬─element_count─┬─source──────────┐
│ products │ Flat │ UInt64 │ ['title'] │ ['String'] │ 23065376 │ 175032 │ ODBC: .products │
└──────────┴──────┴────────┴─────────────────┴─────────────────┴─────────────────┴───────────────┴─────────────────┘
你可以使用 dictGet* 函数来获取这种格式的字典数据。
当你需要获取原始数据,或者是想要使用 JOIN
操作的时候,这种视图并没有什么帮助。对于这些情况,你可以使用 Dictionary
引擎,它可以将字典数据展示在表中。
语法:
CREATE TABLE %table_name% (%fields%) engine = Dictionary(%dictionary_name%)`
示例:
create table products (product_id UInt64, title String) Engine = Dictionary(products);
CREATE TABLE products
(
product_id UInt64,
title String,
)
ENGINE = Dictionary(products)
Ok.
0 rows in set. Elapsed: 0.004 sec.
看一看表中的内容。
select * from products limit 1;
SELECT *
FROM products
LIMIT 1
┌────product_id─┬─title───────────┐
│ 152689 │ Some item │
└───────────────┴─────────────────┘
1 rows in set. Elapsed: 0.006 sec.
文件(输入格式)
数据源是以 Clickhouse 支持的一种输入格式(TabSeparated,Native等)存储数据的文件。
用法示例:
- 从 ClickHouse 导出数据到文件。
- 将数据从一种格式转换为另一种格式。
- 通过编辑磁盘上的文件来更新 ClickHouse 中的数据。
在 ClickHouse 服务器中的使用
File(Format)
选用的 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)
默认情况下,Clickhouse 会创建目录 /var/lib/clickhouse/data/default/file_engine_table
。
2. 手动创建 /var/lib/clickhouse/data/default/file_engine_table/data.TabSeparated
文件,并且包含内容:
$ cat data.TabSeparated
one 1
two 2
3. 查询这些数据:
SELECT * FROM file_engine_table
┌─name─┬─value─┐
│ one │ 1 │
│ two │ 2 │
└──────┴───────┘
在 Clickhouse-local 中的使用
使用 clickhouse-local 时,File 引擎除了 Format
之外,还可以接收文件路径参数。可以使用数字或名称来指定标准输入/输出流,例如 0
或 stdin
,1
或 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"
功能实现
- 读操作可支持并发,但写操作不支持
- 不支持:
ALTER
SELECT ... SAMPLE
- 索引
- 副本
合并
Merge
引擎 (不要跟 MergeTree
引擎混淆) 本身不存储数据,但可用于同时从任意多个其他的表中读取数据。 读是自动并行的,不支持写入。读取时,那些被真正读取到数据的表的索引(如果有的话)会被使用。 Merge
引擎的参数:一个数据库名和一个用于匹配表名的正则表达式。
示例:
Merge(hits, '^WatchLog')
数据会从 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 │
└────────────┴────────┴───────────┴─────┘
虚拟列
虚拟列是一种由表引擎提供而不是在表定义中的列。换种说法就是,这些列并没有在 CREATE TABLE
中指定,但可以在 SELECT
中使用。
下面列出虚拟列跟普通列的不同点:
- 虚拟列不在表结构定义里指定。
- 不能用
INSERT
向虚拟列写数据。 - 使用不指定列名的
INSERT
语句时,虚拟列要会被忽略掉。 - 使用星号通配符(
SELECT *
)时虚拟列不会包含在里面。 - 虚拟列不会出现在
SHOW CREATE TABLE
和DESC TABLE
的查询结果里。
Merge
类型的表包括一个 String
类型的 _table
虚拟列。(如果该表本来已有了一个 _table
的列,那这个虚拟列会命名为 _table1
;如果 _table1
也本就存在了,那这个虚拟列会被命名为 _table2
,依此类推)该列包含被读数据的表名。
如果 WHERE/PREWHERE
子句包含了带 _table
的条件,并且没有依赖其他的列(如作为表达式谓词链接的一个子项或作为整个的表达式),这些条件的作用会像索引一样。这些条件会在那些可能被读数据的表的表名上执行,并且读操作只会在那些满足了该条件的表上去执行。
集合
始终存在于 RAM 中的数据集。它适用于IN运算符的右侧(请参见 «IN运算符» 部分)。
可以使用 INSERT 向表中插入数据。新元素将添加到数据集中,而重复项将被忽略。但是不能对此类型表执行 SELECT 语句。检索数据的唯一方法是在 IN 运算符的右半部分使用它。
数据始终存在于 RAM 中。对于 INSERT,插入数据块也会写入磁盘上的表目录。启动服务器时,此数据将加载到 RAM。也就是说,重新启动后,数据仍然存在。
对于强制服务器重启,磁盘上的数据块可能会丢失或损坏。在数据块损坏的情况下,可能需要手动删除包含损坏数据的文件。
URL(URL,格式)
用于管理远程 HTTP/HTTPS 服务器上的数据。该引擎类似 文件 引擎。
在 ClickHouse 服务器中使用引擎
Format
必须是 ClickHouse 可以用于 SELECT
查询的一种格式,若有必要,还要可用于 INSERT
。有关支持格式的完整列表,请查看 格式。
URL
必须符合统一资源定位符的结构。指定的URL必须指向一个 HTTP 或 HTTPS 服务器。对于服务端响应, 不需要任何额外的 HTTP 头标记。
INSERT
和 SELECT
查询会分别转换为 POST
和 GET
请求。 对于 POST
请求的处理,远程服务器必须支持 分块传输编码。
示例:
1. 在 Clickhouse 服务上创建一个 url_engine_table
表:
CREATE TABLE url_engine_table (word String, value UInt64)
ENGINE=URL('http://127.0.0.1:12345/', CSV)
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()
python3 server.py
3. 查询请求:
SELECT * FROM url_engine_table
┌─word──┬─value─┐
│ Hello │ 1 │
│ World │ 2 │
└───────┴───────┘
功能实现
- 读写操作都支持并发
- 不支持:
ALTER
和SELECT...SAMPLE
操作。- 索引。
- 副本。
视图
用于构建视图(有关更多信息,请参阅 CREATE VIEW 查询
)。 它不存储数据,仅存储指定的 SELECT
查询。 从表中读取时,它会运行此查询(并从查询中删除所有不必要的列)。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库