1.1. ClickHouse
本节介绍 ClickHouse 定义和管理数据库和表结构。
1.1.1.
在SQL数据定义语言( DDL ) 用于创建和修改数据库Schema,例如,表、索引和用户。Schema描述用户数据模型、字段和数据类型。DDL 语句类似于用于定义数据结构的计算机编程语言。DDL 语句包括CREATE、ALTER、DROP等。
ClickHouse SQL中的DDL,函数和字典除了定义数据库、表、索引和视图外,还可以定义。
1.1.2.
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster] [ENGINE = db_engine(...)] [COMMENT 'Comment']
1. 创建名称为db_name的数据库。
2. 如果指定了ON CLUSTER cluster 然后在指定的集群中 cluster 创建所有服务器 db_name 数据库。
3. ENGINE = db_engine(…), 数据库引擎。ClickHouse 默认使用 Atomic 数据库引擎有默认值 ENGINE = Atomic。Atomic 发动机提供可配置 table engines 和 SQL dialect,它支持非阻塞DROP TABLE和RENAME TABLE查询和原子表交换查询命令 EXCHANGE TABLES t1 AND t2。Atomic 中间所有的表都有持久的 UUID,数据存储/数据存储clickhouse_path/store/xxx/xxxyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy/路径下。其中,xxxyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy是表 UUID,不变更支持 UUID 在移动表数据的情况下执行重命名。可以使用DatabaseCatalog,通过 UUID访问 Atomic 数据库中的表。执行DROP TABLE 不删除任何数据,Atomic 数据库只是通过将元数据移动到 /clickhouse_path/metadata_dropped将表标记为删除并通知 DatabaseCatalog。
在 20.5 (可用于版本SELECT version() 查看 ClickHouse版本),ClickHouse 数据库引擎首次引入Atomic。从 20.10 从版本开始,它是默认数据库引擎(以前默认使用过) engine=Ordinary )。SQL 如下:
SELECT version() Query id: 45ca90b4-5e17-4106-a92f-f8fed8822286 ┌─version()────┐ │ 21.12.1.8808 │ └──────────────┘ SHOW CREATE DATABASE system FORMAT TSVRaw Query id: 19d3e06d-dfaf-45b4-a67d-156199098bc4 CREATE DATABASE system ENGINE = Atomic
这两个数据库引擎在文件系统上存储数据的方式不同(Ordinary文件系统件系统布局更简单),Atomic 引擎解决了Ordinary一些引擎问题。
Atomic 引擎支持非阻塞删除表/重命名表、异步表删除和分离(delete&detach)(等待查询完成,但新查询不可见)、原子删除表(删除所有文件/文件夹)、原子表交换(通过EXCHANGE TABLES t1 AND t2表交换)、重命名字典/重命名数据库、
在FS和ZK 独特的中自动生成UUID 复制路径。ClickHouse 支持的数据库引擎可以在 源码目录src/Databases 下面找到。例如在DatabaseFactory.cpp(112行) ClickHouse数据库引擎集合:
database_engines {"Ordinary", "Atomic", "Memory","Dictionary", "Lazy", "Replicated", "MySQL", "MaterializeMySQL", "MaterializedMySQL", "PostgreSQL", "MaterializedPostgreSQL","SQLite"}
4.COMMENT ‘Comment添加数据库注释。所有数据库引擎都支持注释。
数据库引擎
Ordinary
Atomic
文件系统布局
简单
复杂
例如,外部工具支持 clickhouse-backup等
好/成熟
有限/测试版
一些 DDL 查询(DROP / RENAME)可能要挂起来等很久
是的
没有
支持交换 2 张表操作
renamea to a_old,b to a,a_old to b;操作不是原子,操作不是原子 中间可以突破(虽然机会很低)。
EXCHANGE TABLES t1 AND t二、原子操作,无中间状态。
使用 zookeeper路径中的 uuid
不能使用。典型的模式是在需要创建同一张表的新版本时给出zookeeper加上版本后缀的路径。
可以在 zookeeper 路径中使用 uuid。可以在 zookeeper 路径中使用 uuid。但是,扩大集群时要格外小心。Zookeeper 路径更难映射到真实的表。允许任何类型的操作表(重命名、重新创建相同名称等)。
建议始终使用物化视图 TO 语法。
.inner.mv_name, 名字可读。
.inner_id.{uuid},名字不能读。
创建一个使用 Atomic发动机数据库
CREATE DATABASE if not exists clickhouse_tutorial ENGINE = Atomic;
查看数据库列表
使用命令show databases数据库列表如下。
SHOW DATABASES Query id: 08c13dfb-0f5c-4aea-815a-68ec95eaa037 ┌─name────────────────┐ │ INFORMATION_SCHEMA │ │ clickhouse_tutorial │ │ default │ │ information_schema │ │ mydb │ │ system │ │ tutorial │ └─────────────────────┘
假如想看现在ClickHouseServer 在进程实例下,可以使用更详细的数据库列表信息select * from system.databases命令。输出结果可读性如下。
name
engine
data_path
metadata_path
uuid
comment
database
INFORMATION_SCHEMA
Memory
./
00000000-0000-0000-0000-000000000000
clickhouse_tutorial
Atomic
./store/
/Users/chenguangjian/store/3c0/3c0b76c0-1dac-4f88-bc0b-76c01dac3f88/
3c0b76c0-1dac-4f88-bc0b-76c01dac3f88
default
Atomic
./store/
/Users/chenguangjian/store/d62/d62015e0-b943-4090-9620-15e0b9432090/
d62015e0-b943-4090-9620-15e0b9432090
information_schema
Memory
./
00000000-0000-0000-0000-000000000000
mydb
Ordinary
./data/mydb/
/Users/chenguangjian/metadata/mydb/
00000000-0000-0000-0000-000000000000
mydb
system
Atomic
./store/
/Users/chenguangjian/store/268/2682f921-c33f-4278-a682-f921c33f9278/
2682f921-c33f-4278-a682-f921c33f9278
tutorial
Atomic
./store/
/Users/chenguangjian/store/d34/d34824fa-e714-43e8-9348-24fae71403e8/
d34824fa-e714-43e8-9348-24fae71403e8
可以看出,使用Atomic引擎的元数据路径上都带有 UUID,而使用Ordinary引擎的路径使用的是数据库名字,Memory引擎则没有元数据存储到磁盘文件上。
可以使用命令show create database system查看system数据库建库命令:
CREATE DATABASE system
ENGINE = Atomic
可见,system使用的是Atomic引擎。
1.1.3.删除数据库
使用 DROP命令删除数据库,SQL实例如下:
drop DATABASE if exists clickhouse_tutorial;
使用select * from system.databases 查看数据库列表里面,clickhouse_tutorial已经被删掉。
1.1.4.创建****MergeTree表
语法
ClickHouse 中最强大的表引擎是*MergeTree家族系列,也是使用最多的表引擎。
创建MergeTree表SQL语法如下:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster_name]
(
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,
...
PROJECTION projection_name_1 (SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY]),
PROJECTION projection_name_2 (SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY])
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr
[DELETE|TO DISK 'xxx'|TO VOLUME 'xxx' [, ...] ]
[WHERE conditions]
[GROUP BY key_expr [SET v1 = aggr_func(v1) [, v2 = aggr_func(v2) ...]] ] ]
[SETTINGS name=value, ...]
功能说明
1. ON CLUSTER cluster_name,在ClickHouse集群cluster_name上建表。默认情况下,表仅在当前服务器上创建。如果是在ClickHouse集群cluster_name的所有服务节点上建表,使用分布式 DDL 查询子句ON CLUSTER 实现。
2. ENGINE = table_engine,指定表引擎。可以通过SQL查询system.table_engines表内容,获得ClickHouse支持的表引擎。如下所示。
SELECT
name,
supports_ttl
FROM system.table_engines
Query id: f219cd4d-dd2b-4490-a9aa-e66f29cec9ac
┌─name───────────────────────────────────┬─supports_ttl─┐
│ PostgreSQL │ 0 │
│ RabbitMQ │ 0 │
│ Kafka │ 0 │
│ S3 │ 0 │
│ ExecutablePool │ 0 │
│ MaterializedView │ 0 │
│ MaterializedPostgreSQL │ 0 │
│ EmbeddedRocksDB │ 0 │
│ View │ 0 │
│ JDBC │ 0 │
│ Join │ 0 │
│ ExternalDistributed │ 0 │
│ Executable │ 0 │
│ Set │ 0 │
│ Dictionary │ 0 │
│ GenerateRandom │ 0 │
│ LiveView │ 0 │
│ MergeTree │ 1 │
│ Memory │ 0 │
│ Buffer │ 0 │
│ MongoDB │ 0 │
│ URL │ 0 │
│ ReplicatedVersionedCollapsingMergeTree │ 1 │
│ ReplacingMergeTree │ 1 │
│ ReplicatedSummingMergeTree │ 1 │
│ COSN │ 0 │
│ ReplicatedAggregatingMergeTree │ 1 │
│ ReplicatedCollapsingMergeTree │ 1 │
│ File │ 0 │
│ ReplicatedGraphiteMergeTree │ 1 │
│ ReplicatedMergeTree │ 1 │
│ ReplicatedReplacingMergeTree │ 1 │
│ VersionedCollapsingMergeTree │ 1 │
│ SummingMergeTree │ 1 │
│ Distributed │ 0 │
│ TinyLog │ 0 │
│ GraphiteMergeTree │ 1 │
│ SQLite │ 0 │
│ CollapsingMergeTree │ 1 │
│ Merge │ 0 │
│ AggregatingMergeTree │ 1 │
│ ODBC │ 0 │
│ Null │ 0 │
│ StripeLog │ 0 │
│ Log │ 0 │
└────────────────────────────────────────┴──────────────┘
45 rows in set. Elapsed: 0.001 sec.
3.(name1 [type1], … ),指定表字段名和字段数据类型。
4.NULL|NOT NULL,字段是否可空。
5.DEFAULT,指定字段默认值。字段可以通过表达式 DEFAULT expr指定默认值。如果 INSERT 查询没有指定对应的列,则通过计算对应的表达式来填充。例如:`URLDomain String DEFAULT domain(URL)`。如果未定义默认值的表达式,则默认值将设置为数字为零,字符串为空字符串,数组为空数组,日期为 1970-01-01,DateTime unix时间戳为零,NULL为空。
6.MATERIALIZED,物化字段列。这样的字段不能在 INSERT 语句中指定值插入,因为这样的字段总是通过使用其他字段计算出来的。
7.EPHEMERAL,临时字段列。这样的列不存储在表中,不能被SELECT 查询,但可以在 CREATE 语句的默认值中引用。
8.ALIAS,字段别名。该别名值不会存储在表中,SELECT 查询中使用星号时不会被替换。如果在查询解析期间扩展了别名,则可以在 SELECT 中使用它。
9.INDEX index_name1 expr1 TYPE type1(…) GRANULARITY value1,指定索引字段和索引粒度。
10.PROJECTION projection_name_1,指定物化投影字段创建一个 Projection,为当前字段Where查询加速。Projection 思想源自 《C-Store: A Column-oriented DBMS》这篇论文,作者是2015年图灵奖获得者、Vertica 之父,Mike Stonebraker。Projection 意指一组列的组合,可以按照与原表不同的排序存储,并且支持聚合函数的查询。相当于传统意义上的物化视图,用空间换时间。它借鉴 MOLAP 预聚合的思想,在数据写入的时候,根据 PROJECTION定义的表达式,计算写入数据的聚合数据同原始数据一并写入。数据查询的过程中,如果查询SQL通过分析可以通过聚合数据得出,直接查询聚合数据减少计算的开销,解决了由于数据量导致的内存问题。Projection主要分为两种:normal与aggregate。相关源码参阅MergeTreeDataWriter.cpp(574行)MergeTreeDataWriter::TemporaryPart MergeTreeDataWriter::writeProjectionPart()。
11.ORDER BYexpr,指定排序字段元组,也即索引列(ClickHouse中的索引列即排序列)。从左到右顺序,建立稀疏索引组合键。合理设计排序键,对查询性能会有很大提升。一般选择查询条件中,筛选条件频繁的列。可以是单一维度,也可以是组合维度的索引。另外,基数特别大的不适合做索引列,如用户标签表的user_id字段。通常筛选后的数据量在百万级别,性能表现较好。
12.PARTITION BYexpr,指定分区字段元组。分区字段数据存储到独立的文件夹目录下。
13.SAMPLE BYexpr,指定采样字段。
14.compression_codec,指定压缩算法。通用编解码器有NONE(无压缩)、LZ4、LZ4HC、ZSTD。ClickHouse 默认使用 LZ4压缩算法CODEC(LZ4)。专用编解码器有Delta(delta_bytes)、DoubleDelta(适用于时间序列数据)、Gorilla、T64等。另外,使用CODEC(‘AES-128-GCM-SIV’)、CODEC(‘AES-256-GCM-SIV’)等可以加密磁盘上的数据。
15.TTL expr,表数据存活时间表达式。到期数据ClickHouse 会自动清理,对于数据 Part移动或重新压缩,数据的所有行都必须满足“TTL”表达式条件。只能为 MergeTree 系列表指定。TTL 子句不能用于主键列。例如,根据 date字段判断哪些数据到期, TTL表达式为:TTL date + INTERVAL 7DAY,其中 date 为日期字段。那么,date=20220301的数据将会在 7天后,也就是 20220308零点日期被清理。
16.PRIMARY KEY,索引主键。
17.SETTINGS name=value, …,指定配置项name=value,多个配置项之间用逗号分隔。例如,指定表的索引粒度:SETTINGS index_granularity = 8192。
实例讲解
创建MergeTree表
create table if not exists clickhouse_tutorial.user_tag
(
user_id UInt64 DEFAULT 0,
gender String DEFAULT 'NA',
age String DEFAULT 'NA',
active_level String DEFAULT 'NA',
date Date
) engine = MergeTree()
order by (user_id, active_level)
primary key (user_id)
partition by (date);
使用show create table clickhouse_tutorial.user_tag 命令查询建表 SQL,执行结果如下:
SHOW CREATE TABLE clickhouse_tutorial.user_tag
FORMAT TSVRaw
Query id: 30ec8e45-8ead-419e-b8fc-dbce54b5939c
CREATE TABLE clickhouse_tutorial.user_tag
(
`user_id` UInt64 DEFAULT 0,
`gender` String DEFAULT 'NA',
`age` String DEFAULT 'NA',
`active_level` String DEFAULT 'NA',
`date` Date
)
ENGINE = MergeTree
PARTITION BY date
PRIMARY KEY user_id
ORDER BY (user_id, active_level)
SETTINGS index_granularity = 8192
1 rows in set. Elapsed: 0.001 sec.
其中,index_granularity = 8192是默认的稀疏索引间隔行数。
使用PROJECTION 创建MergeTree 表
使用PROJECTION语句建表:
create table if not exists clickhouse_tutorial.user_tag
(
UserID UInt64,
WatchID UInt64,
EventTime DateTime,
Sex UInt8,
Age UInt8,
OS UInt8,
RegionID UInt32,
RequestNum UInt32,
EventDate Date,
PROJECTION pOS(
SELECT
groupBitmap(UserID),
count(1)
GROUP BY OS
),
PROJECTION pRegionID(
SELECT count(1),
groupBitmap(UserID)
GROUP BY RegionID
)
) engine = MergeTree()
order by (WatchID, UserID, EventTime)
partition by (EventDate);
ALTER TABLE****添加PROJECTION
也可以通过alter table 语句添加PROJECTION 定义:
ALTER TABLE clickhouse_tutorial.user_tag
ADD PROJECTION pRegionID(SELECT count(1),groupBitmap(UserID) GROUP BY RegionID);
插入数据:
INSERT INTO clickhouse_tutorial.user_tag
(UserID,
WatchID,
EventTime,
Sex,
Age,
OS,
RegionID,
RequestNum,
EventDate)
select UserID,
WatchID,
EventTime,
Sex,
Age,
OS,
RegionID,
RequestNum,
EventDate
from tutorial.hits_v1;
可以看到 ClickHouse 数据库表文件目录下面多了两个文件夹 pOS.proj和 pRegionID.proj:
.
├── checksums.txt
├── columns.txt
├── count.txt
├── data.bin
├── data.mrk3
├── default_compression_codec.txt
├── minmax_EventDate.idx
├── pOS.proj
│ ├── checksums.txt
│ ├── columns.txt
│ ├── count.txt
│ ├── data.bin
│ ├── data.mrk3
│ ├── default_compression_codec.txt
│ └── primary.idx
├── pRegionID.proj
│ ├── checksums.txt
│ ├── columns.txt
│ ├── count.txt
│ ├── data.bin
│ ├── data.mrk3
│ ├── default_compression_codec.txt
│ └── primary.idx
├── partition.dat
└── primary.idx
2 directories, 23 files
每个proj文件夹下面的数据文件有checksums.txt、columns.txt、count.txt、data.bin、data.mrk3、default_compression_codec.txt、primary.idx等(Projection Part复用表分区信息),跟一张表的数据文件基本一样(可以看出,Projection本身就是表)。其中,pOS.proj/columns.txt文件内容是:
columns format version: 1
2 columns:
`OS` UInt8
`groupBitmap(UserID)` AggregateFunction(groupBitmap, UInt64)
pRegionID.proj/columns.txt文件内容是:
columns format version: 1
2 columns:
`RegionID` UInt32
`groupBitmap(UserID)` AggregateFunction(groupBitmap, UInt64)
手动触发物化PROJECTION
注意,只有在创建 PROJECTION 之后,再被写入的数据,才会自动物化。对于历史数据,需要手动触发物化,执行如下 SQL:
alter table clickhouse_tutorial.user_tag MATERIALIZE PROJECTION pOS;
alter table clickhouse_tutorial.user_tag MATERIALIZE PROJECTION pRegionID;
MATERIALIZE PROJECTION 是一个异步的 Mutation 操作,可以通过下面的语句查询状态:
SELECT
table,
mutation_id,
command,
is_done
FROM system.mutations
Query id: b50679e1-963f-4800-9366-abc08539ca23
┌─table────┬─mutation_id──────┬─command──────────────────────────┬─is_done─┐
│ user_tag │ mutation_127.txt │ MATERIALIZE PROJECTION pOS │ 1 │
│ user_tag │ mutation_128.txt │ MATERIALIZE PROJECTION pRegionID │ 1 │
└──────────┴──────────────────┴──────────────────────────────────┴─────────┘
2 rows in set. Elapsed: 0.004 sec.
等MATERIALIZE PROJECTION 生成好了之后,分区目录如下:
.
├── Age.bin
├── Age.mrk2
├── EventDate.bin
├── EventDate.mrk2
├── EventTime.bin
├── EventTime.mrk2
├── OS.bin
├── OS.mrk2
├── RegionID.bin
├── RegionID.mrk2
├── RequestNum.bin
├── RequestNum.mrk2
├── Sex.bin
├── Sex.mrk2
├── UserID.bin
├── UserID.mrk2
├── WatchID.bin
├── WatchID.mrk2
├── checksums.txt
├── columns.txt
├── count.txt
├── default_compression_codec.txt
├── minmax_EventDate.idx
├── pOS.proj
│ ├── checksums.txt
│ ├── columns.txt
│ ├── count.txt
│ ├── data.bin
│ ├── data.mrk3
│ ├── default_compression_codec.txt
│ └── primary.idx
├── pRegionID.proj
│ ├── checksums.txt
│ ├── columns.txt
│ ├── count.txt
│ ├── data.bin
│ ├── data.mrk3
│ ├── default_compression_codec.txt
│ └── primary.idx
├── partition.dat
└── primary.idx
2 directories, 39 files
看到在原有 MergeTree 的分区下,多了两个pOS.proj和pRegionID.proj子目录,文件存储和 MergeTree 存储格式是一样的,其中,Projection Part复用表分区信息(minmax_EventDate.idx、partition.dat)。Projection写入与原始数据写入过程一致。每一份数据part写入都会基于原始数据Block,结合Projection定义,计算好聚合数据,然后写入对应分区Part文件。
当查询命中某个 PROJECTION 的时候,就会直接用proj子目录中的数据来加速查询。
设置投影优化生效参数
有了PROJECTION 之后,想要查询时投影优化生效,需要设置allow_experimental_projection_optimization参数开启这项功能:
SET allow_experimental_projection_optimization=1;
查看SQL执行计划:
EXPLAIN actions = 1
SELECT
RegionID,
count(1)
FROM clickhouse_tutorial.user_tag
GROUP BY RegionID
Query id: d8a96bb5-c7d5-48bd-98a6-dd148618ef5d
┌─explain───────────────────────────────────────────────────────────────────┐
│ Expression ((Projection + Before ORDER BY)) │
│ Actions: INPUT :: 0 -> RegionID UInt32 : 0 │
│ INPUT :: 1 -> count() UInt64 : 1 │
│ Positions: 0 1 │
│ SettingQuotaAndLimits (Set limits and quota after reading from storage) │
│ ReadFromStorage (MergeTree(with Aggregate projection pRegionID)) │
└───────────────────────────────────────────────────────────────────────────┘
6 rows in set. Elapsed: 0.003 sec.
其中,ReadFromStorage (MergeTree(with Aggregate projection pRegionID)) 表示查询命中 Projection。
如果关闭投影优化,设置如下:
SET allow_experimental_projection_optimization=0;
再次查看 SQL 执行计划,可以看到:
EXPLAIN actions = 1
SELECT
RegionID,
count(1)
FROM clickhouse_tutorial.user_tag
GROUP BY RegionID
Query id: e903857a-4c30-41e6-9a15-082f98dd7cb6
┌─explain───────────────────────────────────────────────────────────────────────┐
│ Expression ((Projection + Before ORDER BY)) │
│ Actions: INPUT :: 0 -> RegionID UInt32 : 0 │
│ INPUT :: 1 -> count() UInt64 : 1 │
│ Positions: 0 1 │
│ Aggregating │
│ Keys: RegionID │
│ Aggregates: │
│ count() │
│ Function: count() → UInt64 │
│ Arguments: none │
│ Argument positions: none │
│ Expression (Before GROUP BY) │
│ Actions: INPUT :: 0 -> RegionID UInt32 : 0 │
│ Positions: 0 │
│ SettingQuotaAndLimits (Set limits and quota after reading from storage) │
│ ReadFromMergeTree │
│ ReadType: Default │
│ Parts: 32 │
│ Granules: 13007 │
└───────────────────────────────────────────────────────────────────────────────┘
19 rows in set. Elapsed: 0.002 sec.
可以看出,就没有命中 Projection。
关于Projection物化投影具体的使用和性能数据,参见“6.7PROJECTION高性能实践”。
1.1.5.复制表
语法
CREATE TABLE [IF NOT EXISTS] [db.]table_name AS [db2.]name2 [ENGINE = engine]
功能说明
使用源表db2.name2结构,创建表db.table_name表,它们具有相同结构。可以为表指定不同的引擎。如果未指定引擎,则使用与 db2.name2 表相同的引擎。
实例讲解
现有表clickhouse_tutorial.user_tag结构如下
CREATE TABLE clickhouse_tutorial.user_tag
(
`UserID` UInt64,
`WatchID` UInt64,
`EventTime` DateTime,
`Sex` UInt8,
`Age` UInt8,
`OS` UInt8,
`RegionID` UInt32,
`RequestNum` UInt32,
`EventDate` Date,
PROJECTION pOS
(
SELECT
groupBitmap(UserID),
count(1)
GROUP BY OS
),
PROJECTION pRegionID
(
SELECT
count(1),
groupBitmap(UserID)
GROUP BY RegionID
)
)
ENGINE = MergeTree
PARTITION BY EventDate
ORDER BY (WatchID, UserID, EventTime)
SETTINGS index_granularity = 8192
我们基于这张表创建一张新表clickhouse_tutorial.user_tag_new,SQL 如下:
CREATE TABLE clickhouse_tutorial.user_tag_new AS clickhouse_tutorial.user_tag;
执行show create table clickhouse_tutorial.user_tag_new 命令:
CREATE TABLE clickhouse_tutorial.user_tag_new
(
`UserID` UInt64,
`WatchID` UInt64,
`EventTime` DateTime,
`Sex` UInt8,
`Age` UInt8,
`OS` UInt8,
`RegionID` UInt32,
`RequestNum` UInt32,
`EventDate` Date,
PROJECTION pOS
(
SELECT
groupBitmap(UserID),
count(1)
GROUP BY OS
),
PROJECTION pRegionID
(
SELECT
count(1),
groupBitmap(UserID)
GROUP BY RegionID
)
)
ENGINE = MergeTree
PARTITION BY EventDate
ORDER BY (WatchID, UserID, EventTime)
SETTINGS index_granularity = 8192
可以看出,这两张表结构一模一样。
1.1.6.从查询语句创建****表
语法
CREATE TABLE [IF NOT EXISTS] [db.]table_name
[(name1 [type1], name2 [type2], ...)]
ENGINE = engine
AS SELECT ...
功能说明
根据SELECT查询数据结构,创建一个表结构相同的表,并把SELECT的数据插入到表中。
实例讲解
1、为了完整展示整个过程,我们先创建一个数据库tutorial,SQL 如下:
CREATE DATABASE IF NOT EXISTS tutorial;
2、然后创建表tutorial.hits_v1,SQL 如下:
CREATE TABLE tutorial.hits_v1
(
`WatchID` UInt64,
`JavaEnable` UInt8,
`Title` String,
`GoodEvent` Int16,
`EventTime` DateTime,
`EventDate` Date,
`CounterID` UInt32,
`ClientIP` UInt32,
`ClientIP6` FixedString(16),
`RegionID` UInt32,
`UserID` UInt64,
`CounterClass` Int8,
`OS` UInt8,
...
`Age` UInt8,
`Sex` UInt8,
...
`ShareURL` String,
`ShareTitle` String,
`ParsedParams` Nested(
Key1 String,
Key2 String,
Key3 String,
Key4 String,
Key5 String,
ValueDouble Float64),
`IslandID` FixedString(16),
`RequestNum` UInt32,
`RequestTry` UInt8
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID);
完整 SQL参见:https://clickhouse.com/docs/en/getting-started/tutorial/。
3、向tutorial.hits_v1表中插入数据。下载 ClickHouse 官网提供的测试数据包:
https://datasets.clickhouse.com/hits/tsv/hits_v1.tsv.xz
解压成.tsv数据文件,然后通过clickhouse client --query命令行导入数据:
clickhouse client
-h 127.0.0.1 --port 9009
-u default
--password 7Dv7Ib0g
--query "INSERT INTO tutorial.hits_v1 FORMAT TSV"
--max_insert_block_size=1000000 < hits_v1.tsv
导入完成之后,可以看看表中数据条数:
SELECT count()
FROM tutorial.hits_v1
Query id: d2207e4b-0dbb-4cac-b498-e009534dc98c
Connecting to 127.0.0.1:9009 as user default.
Connected to ClickHouse server version 22.4.1 revision 54455.
┌─count()─┐
│ 8873898 │
└─────────┘
1 rows in set. Elapsed: 0.067 sec.
4、现在,我们通过使用 SELECT语句基于表tutorial.hits_v1创建一张新表,并把tutorial.hits_v1表中相应字段的数据,插入到新表中。执行下面的建表并插入数据 SQL:
CREATE TABLE clickhouse_tutorial.hits_v2
(
WatchID UInt64,
UserID UInt64,
EventTime DateTime,
OS UInt8,
RegionID UInt32,
RequestNum UInt32,
EventDate Date
) ENGINE = MergeTree
partition by EventDate
order by (WatchID, UserID, EventTime)
AS
SELECT WatchID,
UserID,
EventTime,
OS,
RegionID,
RequestNum,
EventDate
from tutorial.hits_v1;
5、查看创建结果。执行下面的 SQL:
show create table clickhouse_tutorial.hits_v2;
可以看到hits_v2建表SQL 如下:
CREATE TABLE clickhouse_tutorial.hits_v2
(
`WatchID` UInt64,
`UserID` UInt64,
`EventTime` DateTime,
`OS` UInt8,
`RegionID` UInt32,
`RequestNum` UInt32,
`EventDate` Date
)
ENGINE = MergeTree
PARTITION BY EventDate
ORDER BY (WatchID, UserID, EventTime)
SETTINGS index_granularity = 8192
然后,hits_v2中也插入了数据:
SELECT count()
FROM clickhouse_tutorial.hits_v2
Query id: 4a835757-fca2-4866-bba7-27130a1444ec
┌─count()─┐
│ 8873898 │
└─────────┘
1 rows in set. Elapsed: 0.003 sec.
1.1.7.从表函数创建表
语法
CREATE TABLE [IF NOT EXISTS] [db.]table_name AS table_function()
功能说明
创建一个与表函数返回结果相同的表。
实例讲解
ClickHouse 提供了表函数(tablefunction)来构造表。这些表函数如下:
SELECT *
FROM system.table_functions
Query id: 3b768f92-72ca-46bf-b4a3-27e993fae21e
┌─name───────────────┐
│ dictionary │
│ numbers_mt │
│ view │
│ cosn │
│ generateRandom │
│ remote │
│ input │
│ s3Cluster │
│ values │
│ s3 │
│ url │
│ remoteSecure │
│ sqlite │
│ zeros │
│ jdbc │
│ zeros_mt │
│ postgresql │
│ odbc │
│ executable │
│ clusterAllReplicas │
│ cluster │
│ merge │
│ null │
│ file │
│ numbers │
└────────────────────┘
25 rows in set. Elapsed: 0.001 sec.
下面就用实例来说明常用表函数的用法。
file表函数
使用场景
当我们需要把数据从 ClickHouse 导出到文件,或者将数据从一种格式转换为另一种格式,或者通过编辑磁盘上的文件来更新 ClickHouse 中的数据的时候,就可以使用 ClickHouse文件表引擎(FileTableEngine)。
创建文件表引擎
在clickhouse_tutorial数据库中,创建一张使用 File 引擎的表,表名为file_table_demo:
CREATE TABLE clickhouse_tutorial.file_table_demo
(
`name` String,
`value` UInt32
)
ENGINE = File(CSV)
Query id: 235acc8a-11c5-46ca-a90b-e65797d5ba79
Ok.
0 rows in set. Elapsed: 0.003 sec.
执行上面的建表 SQL,创建了文件目录:
/Users/chenguangjian/data/clickhouse_tutorial/file_table_demo
这是一个软连接,指向目录…/store/94a/94a2972d-80c6-4556-94a2-972d80c65556/。
drwxr-x--- 4 chenguangjian staff 128 3 21 22:14 .
drwxr-x--- 8 chenguangjian staff 256 3 21 03:08 ..
lrwxr-x--- 1 chenguangjian staff 64 3 21 22:14 file_table_demo -> /Users/chenguangjian/store/1b1/1b136096-58ae-40e1-9b13-609658ae90e1/
lrwxr-x--- 1 chenguangjian staff 64 3 21 13:06 user_tag -> /Users/chenguangjian/store/94a/94a2972d-80c6-4556-94a2-972d80c65556/
插入数据
手动准备数据文件data.csv,内容如下
$cat data.csv
1,a
2,b
3,c
并把data.csv复制到/Users/chenguangjian/data/clickhouse_tutorial/file_table_demo/ 目录下。这样就完成了表数据的插入。
查询数据
SELECT *
FROM clickhouse_tutorial.file_table_demo
Query id: 0ae85ab2-8188-4b32-8f24-00c5b8b4018a
┌─name─┬─value─┐
│ a │ 1 │
│ b │ 2 │
│ c │ 3 │
└──────┴───────┘
3 rows in set. Elapsed: 0.002 sec.
支持的文件格式
文件表引擎支持TabSeparated、JSON、CSV、Native 等文件格式。完整文件格式清单如下:
SELECT *
FROM system.formats
Query id: 0fde0b5f-b625-4f60-b8cf-6156b66f2ca3
┌─name───────────────────────────────────────┬─is_input─┬─is_output─┐
│ CapnProto │ 1 │ 1 │
│ PostgreSQLWire │ 0 │ 1 │
│ MySQLWire │ 0 │ 1 │
│ JSONStringsEachRowWithProgress │ 0 │ 1 │
│ JSONEachRowWithProgress │ 0 │ 1 │
│ JSONCompact │ 0 │ 1 │
│ Null │ 0 │ 1 │
│ JSONStrings │ 0 │ 1 │
│ JSON │ 0 │ 1 │
...
│ Regexp │ 1 │ 0 │
│ TSV │ 1 │ 1 │
│ Vertical │ 0 │ 1 │
│ CSV │ 1 │ 1 │
│ TSVRaw │ 1 │ 1 │
│ Values │ 1 │ 1 │
│ JSONStringsEachRow │ 1 │ 1 │
│ TabSeparatedWithNamesAndTypes │ 1 │ 1 │
│ TSVRawWithNames │ 1 │ 1 │
│ JSONCompactEachRowWithNamesAndTypes │ 1 │ 1 │
│ TabSeparatedRaw │ 1 │ 1 │
│ TSVWithNames │ 1 │ 1 │
│ JSONEachRow │ 1 │ 1 │
└────────────────────────────────────────────┴──────────┴───────────┘
68 rows in set. Elapsed: 0.002 sec.
numbers(N)表函数
返回具有单个“数字”列 (UInt64) 的表,其中包含从 0 到 N-1 的整数。类似于 system.numbers 表,它可以用于测试和生成连续值。下面 3 个查询是等价的:
SELECT * FROM numbers(10);
SELECT * FROM numbers(0, 10);
SELECT * FROM system.numbers LIMIT 10;
输出:
┌─number─┐
│ 0 │
│ 1 │
│ 2 │
│ 3 │
│ 4 │
│ 5 │
│ 6 │
│ 7 │
│ 8 │
│ 9 │
└────────┘
10 rows in set. Elapsed: 0.001 sec.
remote表函数
连接远程表:
SELECT *
FROM remote('127.0.0.1:9000', clickhouse_tutorial.user_tag)
LIMIT 3
url****表函数
语法:url(URL, format, structure)
功能说明:
URL: String 类型,HTTP 或 HTTPS 服务器地址,可以接收 GET 或 POST 请求
format: String 类型,指定数据的格式
structure:String 类型,确定列名和类型。以格式’UserID UInt64, Name String’指定表结构。
例如,从返回CSV 格式的 HTTPAPI获取包含 String 和 UInt32 类型列的表的前 3 行。
SELECT * FROM url('http://127.0.0.1:8888/', CSV, 'column1 String, column2 UInt32')
LIMIT 3;
h****dfs表函数
语法:hdfs(URI, format, structure)
说明:从 HDFS 文件创建表。此表功能类似于url()表函数。
例如,从hdfs://hdfs1:9000/test查询前 2 行数据:
SELECT *
FROM hdfs('hdfs://hdfs1:9000/test', 'TSV', 'column1 UInt32, column2 UInt32, column3 UInt32')
LIMIT 2
输出:
┌─column1─┬─column2─┬─column3─┐
│ 1 │ 2 │ 3 │
│ 3 │ 2 │ 1 │
└─────────┴─────────┴─────────┘
1.1.8.创建视图
ClickHouse 视图可分为普通视图(Normal View)、物化视图(Materialized View)、实时视图(Live View)和窗口视图(Window View)等。创建视图使用CREATE VIEW命令。
普通视图
普通视图不存储任何数据,在每次访问时从源头表中读取。换句话说,普通视图只不过是保存的查询。
语法
CREATE [OR REPLACE] VIEW [IF NOT EXISTS]
[db.]table_name
[ON CLUSTER]
AS SELECT ...
功能说明
创建普通视图。从普通视图读取时,该查询会被解析替换成 FROM子句的子查询。例如,假设您已经创建了一个视图:
CREATE VIEW my_view AS
SELECT ...
然后,执行视图查询:
SELECT a, b, c FROM my_view
此查询完全等同于使用子查询:
SELECT a, b, c FROM (SELECT ...)
实例讲解
我们先创建一张表clickhouse_tutorial.user_tag,然后插入数据,然后根据这张表创建视图clickhouse_tutorial.user_tag_view。
1 **、**创建源头表
CREATE TABLE clickhouse_tutorial.user_tag
(
`UserID` UInt64,
`WatchID` UInt64,
`EventTime` DateTime,
`Sex` UInt8,
`Age` UInt8,
`OS` UInt8,
`RegionID` UInt32,
`RequestNum` UInt32,
`EventDate` Date,
PROJECTION pOS
(
SELECT
groupBitmap(UserID),
count(1)
GROUP BY OS
),
PROJECTION pRegionID
(
SELECT
count(1),
groupBitmap(UserID)
GROUP BY RegionID
)
)
ENGINE = MergeTree
PARTITION BY EventDate
ORDER BY (WatchID, UserID, EventTime)
SETTINGS index_granularity = 8192
2 **、**插入数据
INSERT INTO clickhouse_tutorial.user_tag
(UserID,
WatchID,
EventTime,
Sex,
Age,
OS,
RegionID,
RequestNum,
EventDate)
select UserID,
WatchID,
EventTime,
Sex,
Age,
OS,
RegionID,
RequestNum,
EventDate
from tutorial.hits_v1;
3、创建普通视图
计算源头表clickhouse_tutorial.user_tag中的uniqExact(UserID) 、Age,执行如下 SQL创建普通视图user_tag_view:
CREATE VIEW clickhouse_tutorial.user_tag_view AS
SELECT
uniqExact(UserID) AS userCnt,
Age AS age
FROM clickhouse_tutorial.user_tag
GROUP BY Age
视图看起来与普通表相同。例如,视图也在“SHOW TABLES”查询的结果中。执行如下 SQL 查看视图:
USE clickhouse_tutorial
SHOW TABLES
Query id: 94bc8ce6-37e7-489b-9a98-07f0d6cc0d71
┌─name──────────┐
│ hits_v2 │
│ user_tag │
│ user_tag_new │
│ user_tag_view │
└───────────────┘
4 rows in set. Elapsed: 0.002 sec.
可以看到,视图也是作为 Table 在列表里返回。但是,我们去 ClickHouse存储表数据的文件目录/Users/data/clickhouse/data/clickhouse_tutorial 查看,并没有user_tag_view文件夹:
$ls -la | awk '{print $9 $10 $11}'
.
..
hits_v2->/Users/data/clickhouse/store/621/62181267-82f4-438d-9d1b-18437c1a86c0/
user_tag->/Users/data/clickhouse/store/daf/daf4def5-8bdc-4185-9674-3d406a37889a/
user_tag_new->/Users/data/clickhouse/store/d2c/d2c144a3-ed75-4db4-ac50-daab9b659427/
我们先记下user_tag表对应的文件目录 UUID,后面查询视图数据的时候,我们可以通过查看 ClickHouseServer 端的日志,确认查询视图数据,最终走的还是查询视图源表的数据。
4 **、**查询视图数据
执行下面的 SQL 查询视图clickhouse_tutorial.user_tag_view里面的数据:
SELECT
userCnt,
age
FROM clickhouse_tutorial.user_tag_view
Query id: 96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6
┌─userCnt─┬─age─┐
│ 82404 │ 0 │
│ 21066 │ 16 │
│ 6556 │ 22 │
│ 2129 │ 26 │
│ 6282 │ 39 │
│ 8311 │ 55 │
└─────────┴─────┘
6 rows in set. Elapsed: 0.127 sec. Processed 8.87 million rows, 79.87 MB (69.64 million rows/s., 626.72 MB/s.)
我们去 ClickHouseServer 端看一下请求日志,搜索Query id关键字96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6即可搜到:
2022.03.23 03:38:36.268801 [ 2266438 ] {} TCP-Session: 39f75155-4c37-46dc-93ce-95fbabe57f61 Creating query context from session context, user_id: 94309d50-4f52-5250-31bd-74fecac179db, parent context user: default
2022.03.23 03:38:36.268938 [ 2266438 ] {96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6}
2022.03.23 03:38:36.269120 [ 2266438 ] {96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6}
2022.03.23 03:38:36.269439 [ 2266438 ] {96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6}
2022.03.23 03:38:36.270055 [ 2266438 ] {96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6}
2022.03.23 03:38:36.270101 [ 2266438 ] {96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6} InterpreterSelectQuery: FetchColumns -> Complete
2022.03.23 03:38:36.270178 [ 2266438 ] {96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6}
…
2022.03.23 03:38:36.270884 [ 2266438 ] {96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6}
2022.03.23 03:38:36.271363 [ 2266438 ] {96bcbd61-0a4d-4d18-9e04-e3896b7e9ad6}
…
2022.03.23 03:38:36.395867 [ 2266438 ] {} TCPHandler: Processed in 0.12711 sec.
从 ClickHouseServer 端的查询处理日志可以看出,查询视图 SQL 最终执行的逻辑,还是到源头表clickhouse_tutorial.user_tag (daf4def5-8bdc-4185-9674-3d406a37889a) 文件目录下面去读取相应的数据。
物化视图
物化视图跟普通视图的区别是,物化视图有自己的物理数据文件存储,而普通视图只是一层逻辑查询代理。
语法
CREATE MATERIALIZED VIEW [IF NOT EXISTS]
[db.]mt_table_name
[ON CLUSTER] [TO[db.]name]
[ENGINE = engine]
[POPULATE]
AS SELECT ...
功能说明
1.创建名称为mt_table_name的物化视图。
2.物化视图支持表引擎,数据的保存形式由表的引擎决定。
a)如果想把物化视图数据写入特定物理表数据文件中,可以使用TO [db].[table]。因为物化视图数据文件目录的生成规则是.inner_id.UUID,可读性比较差。
b)如果指定TO db.table,不能同时使用 POPULATE,也不需要指定表引擎ENGINE= engine(因为db.table表结构已经创建好了,表引擎也指定好了)。
c)如果没有指定TO db.table,则必须指定用于存储物化视图mt_table_name物理数据的表引擎ENGINE= engine。
3.物化视图在插入目标表期间,使用列名而不是列顺序。如果 SELECT查询结果中不存在某些列名,使用默认值。SELECT 查询可以包含DISTINCT、GROUP BY、ORDER BY、LIMIT。
4.创建完物化视图,源表被写入新数据则物化视图也会同步更新。
a)如果指定POPULATE,则在创建视图时会将现有表数据插入到视图中。
b)如果不指定POPULATE,SELECT查询结果只包含创建视图后插入表中的数据。
c)不建议使用 POPULATE,因为在创建物化视图的过程中,同时写入的数据不能被插入物化视图。
5.建议在使用物化视图时,为每一列添加别名。
6.物化视图不支持UNION。
Populate,vt.居住于;生活于;构成…的人口;迁移;移居;殖民于;给文件增添数据,输入数据。
显然,物化视图里关键字POPULATE的意思是“给文件增添数据,输入数据”。
实例讲解
创建物化视图
从源表user_tag创建一个物化视图user_tag_mt_view_1,SQL 如下:
create materialized view clickhouse_tutorial.user_tag_mt_view_1
engine = MergeTree
partition by (date)
order by (age, date)
populate
as
select uniqExact(UserID) userCnt,
Age age,
EventDate date
from clickhouse_tutorial.user_tag
group by Age, EventDate;
为了方便测试,我们使用了populate同步源表user_tag中已有的数据到物化视图user_tag_mt_view_1中。
查询物化视图详细信息
创建完成之后,可以到system.tables系统表里查看物化视图表的详细信息。如下:
SELECT *
FROM system.tables
WHERE (database = 'clickhouse_tutorial') AND (name = 'user_tag_mt_view_1')
FORMAT Vertical
Query id: 84e4bdd5-5cf2-487b-8f79-512bd766e1dd
Row 1:
──────
database: clickhouse_tutorial
name: user_tag_mt_view_1
uuid: 1c93ef11-704f-4515-9596-fbdb4efdf6b8
engine: MaterializedView
is_temporary: 0
data_paths: ['/Users/data/clickhouse/store/2b4/2b4de01f-12f8-443e-97aa-53ecd1aa49a5/']
metadata_path: /Users/data/clickhouse/store/1e0/1e031223-783a-48a9-9f32-1a719033081b/user_tag_mt_view_1.sql
metadata_modification_time: 2022-03-23 15:36:51
dependencies_database: []
dependencies_table: []
create_table_query: CREATE MATERIALIZED VIEW clickhouse_tutorial.user_tag_mt_view_1 (`userCnt` UInt64, `age` UInt8, `date` Date) ENGINE = MergeTree PARTITION BY date ORDER BY (age, date) SETTINGS index_granularity = 8192 AS SELECT uniqExact(UserID) AS userCnt, Age AS age, EventDate AS date FROM clickhouse_tutorial.user_tag GROUP BY Age, EventDate
engine_full: MergeTree PARTITION BY date ORDER BY (age, date) SETTINGS index_granularity = 8192
as_select: SELECT uniqExact(UserID) AS userCnt, Age AS age, EventDate AS date FROM clickhouse_tutorial.user_tag GROUP BY Age, EventDate
partition_key:
sorting_key:
primary_key:
sampling_key:
storage_policy:
total_rows: ????
total_bytes: ????
lifetime_rows: ????
lifetime_bytes: ????
comment:
has_own_data: 0
loading_dependencies_database: []
loading_dependencies_table: []
loading_dependent_database: []
loading_dependent_table: []
1 rows in set. Elapsed: 0.004 sec.
去 ClickHouse 数据库的数据文件目录/Users/data/clickhouse/data/clickhouse_tutorial 下面,可以看到多了一个目录:
%2Einner_id%2E1c93ef11%2D704f%2D4515%2D9596%2Dfbdb4efdf6b8 -> /Users/data/clickhouse/store/2b4/2b4de01f-12f8-443e-97aa-53ecd1aa49a5/
上面的%2E,%2D是转义之后的字符,我们使用 JS脚本函数
decodeURI(‘%2Einner_id%2E1c93ef11%2D704f%2D4515%2D9596%2Dfbdb4efdf6b8’)
解码成可读的字符,就是:
.inner_id.
.inner_id.后面的这一串,这个正是上面system.tables中查询结果里的uuid: 1c93ef11-704f-4515-9596-fbdb4efdf6b8。物化视图本质上也是一张内置的物理表。关于内置表名的生成规则可以在源代码StorageMaterializedView.cpp(39行) generateInnerTableName()函数中看到:
static inline String generateInnerTableName(const StorageID & view_id)
{
if (view_id.hasUUID())
return ".inner_id." + toString(view_id.uuid);
return ".inner." + view_id.getTableName();
}
查询物化视图数据
执行如下 SQL 查询物化视图数据:
SELECT
userCnt,
age,
date
FROM clickhouse_tutorial.user_tag_mt_view_1
ORDER BY
age ASC,
date ASC
Query id: 1027e983-2046-442d-aeb2-0ad770adbccc
┌─userCnt─┬─age─┬───────date─┐
│ 15578 │ 0 │ 2014-03-17 │
│ 15965 │ 0 │ 2014-03-18 │
...
│ 3339 │ 55 │ 2014-03-23 │
└─────────┴─────┴────────────┘
42 rows in set. Elapsed: 0.004 sec.
跟源表数据对比
跟源表中的数据对比一下:
SELECT
uniqExact(UserID) AS userCnt,
Age AS age,
EventDate AS date
FROM clickhouse_tutorial.user_tag
GROUP BY
Age,
EventDate
ORDER BY
Age ASC,
EventDate ASC
Query id: 4c700ac2-e5ff-420b-8a8d-5e6f870aa7c9
┌─userCnt─┬─age─┬───────date─┐
│ 15578 │ 0 │ 2014-03-17 │
│ 15965 │ 0 │ 2014-03-18 │
...
│ 3339 │ 55 │ 2014-03-23 │
└─────────┴─────┴────────────┘
42 rows in set. Elapsed: 0.124 sec. Processed 8.87 million rows, 97.61 MB (71.48 million rows/s., 786.28 MB/s.)
可以看出,物化视图跟源表数据查询结果是一致的。
性能对比
物化视图的核心思想也是预聚合、用空间换时间、数据立方体的思想,主要就是用来提升查询性能的。我们简单对比一下使用物化视图跟使用源表实时查询的性能数据。如下表。
可以看出,物化视图查询性能相对于源表聚合查询,性能上有几十数百倍的提升(由数据量大小、聚合方式、查询聚合字段基数等因素影响)。
指定物理表存储物化视图数据
可以指定用一张物理表来存储物化视图的数据。
1、先创建用来存储物化视图数据的目标表:
create table clickhouse_tutorial.user_tag_mt_view_2_table
(
date Date,
age UInt8,
userCnt UInt64
) engine = MergeTree
partition by (date)
order by (age, date);
2、然后,创建物化视图,并指定目标表存储物化视图的数据,SQL如下:
create materialized view clickhouse_tutorial.user_tag_mt_view_2
to clickhouse_tutorial.user_tag_mt_view_2_table
as
select uniqExact(UserID) userCnt,
Age age,
EventDate date
from clickhouse_tutorial.user_tag
group by Age, EventDate;
此时,指定了to clickhouse_tutorial.user_tag_mt_view_2_table,不能使用populate。
3、我们去system.tables中看一下user_tag_mt_view_2与user_tag_mt_view_2_table详情信息,执行如下 SQL:
SELECT
name,
uuid,
engine,
data_paths
FROM system.tables
WHERE (database = 'clickhouse_tutorial') AND (name IN ('user_tag_mt_view_2', 'user_tag_mt_view_2_table'))
输出结果:
name
uuid
engine
data_paths
user_tag_mt_view_2
e100e762-a590-4c49-8e3a-e9798f05458e
MaterializedView
user_tag_mt_view_2_table
b16f114d-1423-45c0-adb2-56d0b9705c00
MergeTree
可以发现,ClickHouse 维护了两份逻辑元数据(name,uuid,engine)等,但是物理数据文件是同一个,都是/Users/data/clickhouse/store/b16/b16f114d-1423-45c0-adb2-56d0b9705c00/。
另外,我们到数据库clickhouse_tutorial存储表数据的文件目录/Users/data/clickhouse/data/clickhouse_tutorial 下面可以发现,针对物化视图user_tag_mt_view_2 并没有再生成.inner_id.UUID这样的数据文件了。执行如下命令查看文件列表:
$ls -la|awk '{print $9 $10 $11}'
%2Einner_id%2E1c93ef11%2D704f%2D4515%2D9596%2Dfbdb4efdf6b8->/Users/data/clickhouse/store/2b4/2b4de01f-12f8-443e-97aa-53ecd1aa49a5/
.
..
hits_v2->/Users/data/clickhouse/store/621/62181267-82f4-438d-9d1b-18437c1a86c0/
user_tag->/Users/data/clickhouse/store/daf/daf4def5-8bdc-4185-9674-3d406a37889a/
user_tag_mt_view_2_table->/Users/data/clickhouse/store/b16/b16f114d-1423-45c0-adb2-56d0b9705c00/
user_tag_new->/Users/data/clickhouse/store/d2c/d2c144a3-ed75-4db4-ac50-daab9b659427/
4、为了测试数据查询效果,我们往源表里面插入数据
INSERT INTO clickhouse_tutorial.user_tag
(UserID,
WatchID,
EventTime,
Sex,
Age,
OS,
RegionID,
RequestNum,
EventDate)
select UserID,
WatchID,
EventTime,
Sex,
Age,
OS,
RegionID,
RequestNum,
EventDate
from tutorial.hits_v1;
5、从 Server 端的日志中,我们可以看到如下内容:
2022.03.23 16:40:41.321635 [ 2265628 ] {6e0011e2-a841-4fd8-b0f7-08d57ef764f0} clickhouse_tutorial.user_tag_mt_view_2_table (b16f114d-1423-45c0-adb2-56d0b9705c00): Renaming temporary part tmp_insert_20140321_1_1_0 to 20140321_1_1_0.
2022.03.23 16:40:41.322075 [ 2265628 ] {6e0011e2-a841-4fd8-b0f7-08d57ef764f0} DiskLocal: Reserving 1.00 MiB on disk `default`, having unreserved 195.81 GiB.
…
2022.03.23 16:40:41.345182 [ 2265628 ] {6e0011e2-a841-4fd8-b0f7-08d57ef764f0} DiskLocal: Reserving 1.00 MiB on disk `default`, having unreserved 195.81 GiB.
2022.03.23 16:40:41.345697 [ 2265628 ] {6e0011e2-a841-4fd8-b0f7-08d57ef764f0} MergedBlockOutputStream: filled checksums 20140317_14_14_0 (state Temporary)
2022.03.23 16:40:41.346478 [ 2265628 ] {6e0011e2-a841-4fd8-b0f7-08d57ef764f0} clickhouse_tutorial.`.inner_id.1c93ef11-704f-4515-9596-fbdb4efdf6b8` (2b4de01f-12f8-443e-97aa-53ecd1aa49a5): Renaming temporary part tmp_insert_20140319_13_13_0 to 20140319_13_13_0.
…
2022.03.23 16:40:41.347180 [ 2265628 ] {6e0011e2-a841-4fd8-b0f7-08d57ef764f0} PushingToViews: Pushing (sequentially) from clickhouse_tutorial.user_tag (daf4def5-8bdc-4185-9674-3d406a37889a) to clickhouse_tutorial.user_tag_mt_view_2 (e100e762-a590-4c49-8e3a-e9798f05458e) took 160 ms.
2022.03.23 16:40:41.347210 [ 2265628 ] {6e0011e2-a841-4fd8-b0f7-08d57ef764f0} PushingToViews: Pushing (sequentially) from clickhouse_tutorial.user_tag (daf4def5-8bdc-4185-9674-3d406a37889a) to clickhouse_tutorial.user_tag_mt_view_1 (1c93ef11-704f-4515-9596-fbdb4efdf6b8) took 145 ms.
可以发现,插入源表数据的过程中,会执行物化视图数据