ClickHouse SQL基本语法和导入导出实战

2022/03 作者:ihunter 0 0

一、SQL基本语法

1.1、CREATE

1.1.1、CREATE DATABASE

数据库起到了命名空间的作用,可以有效规避命名冲突的问题,也为后续的数据隔离提供了支撑。任何一张数据表,都必须归属在某个数据库之下。

创建数据库的完整语法如下所示:

CREATE DATABASE [IF NOT EXISTS] db_name [ENGINE = engine];

如果查询中存在 IF NOT EXISTS,则当数据库已经存在时,该查询不会返回任何错误。

create database if not exists db_name;

切换数据库使用:

use db_name;

[ENGINE = engine] 表示数据库所使用的引擎类型(是的,你没看错,数据库也支持设置引擎)

数据库目前一共支持 5 种引擎,如下所示:

  • Ordinary: 默认引擎,在绝大多数情况下我们都会使用默认引擎,使用时无须刻意声明。在此数据库下可以 使用任意类型的表引擎。

  • Dictionary: 字典引擎,此类数据库会自动为所有数据字典创建它们的数据表。

  • Memory: 内存引擎,用于存放临时数据。此类数据库下的数据表只会停留在内存中,不会涉及任何磁盘操 作,当服务重启后数据会被清除。

  • Lazy: 日志引擎,此类数据库下只能使用Log系列的表引擎。

  • MySQL: MySQL引擎,此类数据库下会自动拉取远端MySQL中的数据,并为它们创建MySQL表引擎的数据表

在绝大多数情况下都只需使用默认的数据库引擎,默认数据库的实质是物理磁盘上的一个文件目录,所以在语句执行之后,ClickHouse便会在安装路径下创建DB_TEST 数据库的文件目录

cd /var/lib/clickhouse/data
ll
库名:db_name, 这个库的数据存储目录:/var/lib/clickhouse/data/db_name
表名:test, 这个表的数据存储目录:/var/lib/clickhouse/data/db_name/test/
分区:20210425,这个分区的存储目录:/var/lib/clickhouse/data/db_name/test/20210425

与此同时,在metadata路径下也会一同创建用于恢复数据库的 DB_TEST.sql 文件:

/var/lib/clickhouse/metadata
ll

使用 SHOW DATABASES 查询,即能够返回 ClickHouse 当前的数据库列表,使用 USE 查询可以实现在多个数据库之间进行切换,而通过 SHOW TABLES 查询可以查看当前数据库的数据表列表。删除一个数据库,则需要用到DROP(别瞎搞~)。

drop database [if exists] db_name

1.1.2. CREATE TABLE

对于创建表,语法如下:

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

有三种方式创建表:

1、直接创建

create table table_name(    id UInt16,    name String) engine=TinyLog;

2、创建一个与其他表有相同结构的表,属于表复制(复制表)

CREATE TABLE [IF NOT EXISTS] [db.]table_name AS [db2.]name2 [ENGINE = engine]

可以对其指定不同的表引擎声明。如果没有表引擎声明,则创建的表将与 db2.name2 使用相同的表引擎。

create table table_name1 as table_name2 engine=Memory;
desc table_name2;

3、使用指定的引擎创建一个与 SELECT 子句的结果具有相同结构的表,并使用 SELECT 子句的结果填充它。

语法:在Hive中,也支持这种语法,简称为 CTAS: create table .... as select ...

CREATE TABLE [IF NOT EXISTS] [db.]table_name ENGINE = engine AS SELECT ...

1.1.3. 表字段

参数说明:表字段支持三种默认值表达式的定义方法,分别是DEFAULT、MATERIALIZED和ALIAS。

无论使用哪种形式,表字段一旦被定义了默认值,它便不再强制要求定义数据类型,因为 ClickHouse 会 根据默认值进行类型推断。如果同时对表字段定义了数据类型和默认值表达式,则以明确定义的数据类型为主。

ClickHouse SQL基本语法和导入导出实战


默认值表达式的三种定义方法之间也存在着不同之处,可以从如下三个方面进行比较。

  1. 数据写入:在数据写入时,只有DEFAULT类型的字段可以出现在 INSERT 语句中。而 MATERIALIZED 和ALIAS 都不能被显式赋值,它们只能依靠计算取值。例如试图为 MATERIALIZED 类 型的字段写入数据,将会得到如下的错误

DB::Exception: Cannot insert column URL,because it is MATERIALIZED column..
  1. 数据查询:在数据查询时,只有 DEFAULT 类型的字段可以通过 SELECT * 返回。而 MATERIALIZED 和ALIAS 类型的字段不会出现在 SELECT * 查询的返回结果集中。

  2. 数据存储:在数据存储时,只有 DEFAULT 和 MATERIALIZED 类型的字段才支持持久化。如果使用的表引擎支持物理存储(例如 TinyLog 表引擎),那么这些列字段将会拥有物理存储。而 ALIAS 类型 的字段不支持久化,它的取值总是需要依靠计算产生,数据不会落到磁盘。

可以使用 ALTER 语句修改默认值,例如:

ALTER TABLE [db_name.]table MODIFY COLUMN col_name DEFAULT value;

修改动作并不会影响数据表内先前已经存在的数据。但是默认值的修改有诸多限制,例如在合并树表引擎中,它的主键字段是无法被修改的;而某些表引擎则完全不支持修改(例如 TinyLog)。

1.1.4. 临时表

ClickHouse 也有临时表的概念,创建临时表的方法是在普通表的基础之上添加 TEMPORARY 关键字, 它的完整语法如下所示:

CREATE TEMPORARY TABLE [IF NOT EXISTS] table_name (
    name1 [type][DEFAULT|MATERIALIZED|ALIAS expr],
    name2 [type][DEFAULT|MATERIALIZED|ALIAS expr]
)

相比普通表而言,临时表有如下几点特殊之处:

  1. 它的生命周期是会话绑定的,所以它只支持Memory表引擎,如果会话结束,数据表就会被销毁;

  2. 临时表不属于任何数据库,所以在它的建表语句中,既没有数据库参数也没有表引擎参数。

  3. 临时表的优先级是大于普通表的。当两张数据表名称相同的时候,会优先读取临时表的数据。

  4. 在 ClickHouse 的日常使用中,通常不会刻意使用临时表。它更多被运用在 ClickHouse 的内部,是数据在集群间传播的载体。

1.1.5. 分区表

Hive可以创建分区表,分桶表。

  • 分区表:把数据按照人为的方式进行分类管理,每个类别就是一个文件夹

  • 分桶表:按照指定的程序逻辑来决定哪些数据被分到哪些桶。每个桶是一个文件。

数据分区(partition)和数据分片(shard)是完全不同的两个概念。数据分区是针对本地数据而言 的,是数据的一种纵向切分。而数据分片是数据的一种横向切分。

数据分区对于一款 OLAP 数据库而言意义非凡:借助数据分区,在后续的查询过程中能够跳过不必要的数据目录,从而提升查询的性能。合理地利用分区特性,还可以变相实现数据的更新操作,因为数据分区支持删除、替换和重置操作。假设数据表按照月份分区,那么数据就可以按月份的粒度被替换更新。分区虽好,但不是所有的表引擎都可以使用这项特性,目前只有合并树(MergeTree)家族系列的表引擎才支持数据分区。

接下来通过一个简单的例子演示分区表的使用方法。

首先由 PARTITION BY 指定分区键,例如下面的数据表 nx_table_partition1 使用了日期字段作为分区键,并将其格式化为年月的形式:

create table table_test_partition(    id String,    url String,
    eventTime DateTime
) engine = MergeTree()partition by toYYYYMM(eventTime)order by id;

通过 system.parts 系统表,查询数据表的分区状态:

select table, partition, path from system.parts where table ='table_test_partition';

可以看到,table_test_partition 按年月划分后,目前拥有的数据分区,且每个分区都对应一个独立的文件目录,用于保存各自部分的数据。

合理设计分区键非常重要,通常会按照数据表的查询场景进行针对性设计。例如在刚才的示例中数据表 按年月分区,如果后续的查询按照分区键过滤,例如:

select * from table_test_partition where eventTime = '2021-04-25';

那么在后续的查询过程中,可以利用分区索引跳过5月份和6月份的分区目录,只加载5月份的数据,从 而带来查询的性能提升。当然,使用不合理的分区键也会适得其反,分区键不应该使用粒度过细的数据 字段。例如,按照小时分区,将会带来分区数量的急剧增长,从而导致性能下降。

1.2. ClickHouse视图

Hive中的视图: 只有逻辑视图

ClickHouse的视图: 物化视图的概念 ClickHouse的物化视图,就类似于触发器

ClickHouse 拥有普通和物化两种视图,其中物化视图拥有独立的存储,而普通视图只是一层简单的查询代理。创建普通视图的完整语法如下所示:

CREATE VIEW [IF NOT EXISTS] [db_name.]view_name AS SELECT ...

普通视图不会存储任何数据,它只是一层单纯的 SELECT 查询映射,起着简化查询、明晰语义的作用, 对查询性能不会有任何增强。假设有一张普通视图 view_tb_v1,它是基于数据表 tb_v1 创建的,那么下面的两条 SELECT 查询是完全等价的:

--普通表
SELECT * FROM tb_v1
-- tb_v1的视图
SELECT * FROM view_tb_v1

物化视图支持表引擎,数据保存形式由它的表引擎决定,创建物化视图的完整语法如下所示:

CREATE [MATERIALIZED] VIEW [IF NOT EXISTS] [db.]table_name [To [db.]name][ENGINE=engine] [POPULATE] AS SELECT ...

物化视图创建好之后,如果源表被写入新数据,那么物化视图也会同步更新。

POPULATE 修饰符决定了物化视图的初始化策略:如果使用了 POPULATE 修饰符,那么在创建视图的过程中,会连带将源表中已存在的数据一并导入,如同执行了 SELECT INTO 一般;反之,如果不使用 POPULATE 修饰符,那么 物化视图在创建之后是没有数据的,它只会同步在此之后被写入源表的数据。物化视图目前并不支持同步删除,如果在源表中删除了数据,物化视图的数据仍会保留。

物化视图本质是一张特殊的数据表,例如使用 SHOW TABLE 查看数据表的列表:

show tables;

由上可以发现,物化视图也在其中,它们是使用了 .inner 特殊前缀的数据表,所以删除视图的方法是直接使用

DROP TABLE

1.3. ALTER

目前只有 MergeTree、Merge 和 Distributed 这三类表引擎支持 ALTER 查询,基本语法:

ALTER TABLE [db].name [ON CLUSTER cluster] ADD|DROP|MODIFY COLUMN ...

参数解析:

ADD COLUMN – 向表中添加新列 
DROP COLUMN – 在表中删除列 
MODIFY COLUMN – 更改列的类型

查询列的信息:

desc table_name;

用法如下:

添加字段:

对于数据表中已经存在的旧数据而言,新追加的字段会使用默认值补全。

alter table [table_name] [add] [column] age UInt8;
或者alter table [table_name] [add] [column] age default 18;
或者alter table [table_name] [add] [column] age UInt8 after gender;

更改 age 列的类型:

修改某个字段的数据类型,实质上会调用相应的toType转型方法。如果当前的类型与期望的类型不能兼容,则修改操作将会失败

alter table [table_name] [modify] [column] age UInt16;

删除age列:

alter table [table_name] [drop] [column] age;

修改备注:

alter table [table_name] [comment] column age '年龄';

移动数据表:

需要注意的是,数据表的移动只能在单个节点的范围内。换言之,数据表移动的目标数据库和原始数据库必须处在同一个服务节点内,而不能是集群中的远程节点。

rename table dylan.test_table1 to dylan.test_table2;

1.4. DESCRIBE TABLE

desc [table_name];

1.5. Partition

创建一个分区表:

--创建新分区表
create table dylan_table_partition(    
    id String,
    uname String,
    create_time Date
) engine = MergeTree()
partition by toYYYYMM(create_time)
order by id;

查询分区信息

ClickHouse 内置了许多 system 系统表,用于查询自身的状态信息。其中 parts 系统表专门用于查询数据表的分区信息

select partition_id, name, table, database, path from system.parts where table ='dylan_table_partition';

删除分区

alter table dylan_table_partition drop partition '20210425';

复制分区

alter table dylan_table_partition1 replace partition '20210425' from dylan_table_partition;

重置分区

如果数据表某一列的数据有误,需要将其重置为初始值,此时可以使用下面的语句实现:

ALTER TABLE dylan_table_partition1 CLEAR COLUMN column_name IN PARTITION partition_expr;

卸载和装载分区

表分区可以通过 DETACH 语句卸载,分区被卸载后,它的物理数据并没有删除,而是被转移到了当前 数据表目录的 detached 子目录下。而装载分区则是反向操作,它能够将 detached 子目录下的某个分 区重新装载回去。卸载与装载这一对伴生的操作,常用于分区数据的迁移和备份场景。

一旦分区被移动到了 detached 子目录,就代表它已经脱离了 ClickHouse 的管理,ClickHouse 并不会主动清理这些文件。这些分区文件会一直存在,除非我们主动删除或者使用 ATTACH 语句重新装载它们。

Hive中有类似的概念: offline

--卸载
ALTER TABLE dylan_test_partition DETACH PARTITION partition_expr
--装载
ALTER TABLE dylan_test_partition ATTACH PARTITION partition_expr

1.6. CHECK TABLE

检查表中的数据是否损坏,他会返回两种结果:

该命令只支持 Log,TinyLog 和 StripeLog 引擎。
check table dylan_test_partition;
0 – 数据已损坏
1 – 数据完整

1.7. 分布式DDL执行

HDFS:datanode单独启动堆外提供服务么? 不可以

ClickHouse: 每个节点事实上,本质上,依然是独立的。如果你真的需要在所有的clickhouse节点上,去创建一张表:你需要把创建表的命令,在每个clickhouse server中执行一次。

ClickHouse 支持集群模式,一个集群拥有 1 到多个节点。CREATE、ALTER、DROP、RENMAE 及 TRUNCATE 这些 DDL 语句,都支持分布式执行。这意味着,如果在集群中任意一个节点上执行 DDL 语 句,那么集群中的每个节点都会以相同的顺序执行相同的语句。这项特性意义非凡,它就如同批处理命 令一样,省去了需要依次去单个节点执行 DDL 的烦恼。

将一条普通的 DDL 语句转换成分布式执行十分简单,只需加上 ON CLUSTERcluster_name 声明即可。例如,执行下面的语句后将会对 dp_cluster 集群内的所有节点广播这条 DDL 语句:

-- 分布式建库
create database if not exists dylan on CLUSTER dp_cluster;
-- 分布式建表CREATE TABLE dylan_test_partition ON CLUSTER dp_cluster(    
    id String,
    uname String,
    create_time Date
)ENGINE=MergeTree()
PARTITION BY toYYYYMM(create_time)
ORDER BY id;

2. ClickHouse 导入导出

2.1. INSERT INTO

创建一张表:

drop table if exists dylan_test;
create table dylan_test(
    id UInt16,
    name String
) engine=TinyLog;
drop table if exists dylan_test1;
create table dylan_test1(
    id UInt16,
    name String
) engine = TinyLog;

向表中添加数据,基本格式如下:

INSERT INTO [db.]table [(c1, c2, c3)] VALUES (v11, v12, v13), (v21, v22, v23),...

在使用 VALUES 格式的语法写入数据时,支持加入表达式或函数,例如:

insert into dylan_test values(1, toString(1+1));

还可以使用 select 来写入数据:

INSERT INTO [db.]table [(c1, c2, c3)] SELECT ...

ClickHouse 不支持的修改数据的查询:UPDATE, DELETE, REPLACE, MERGE, UPSERT, INSERT UPDATE.

插入 JSON 格式的数据:

INSERT INTO dylan_test1 FORMAT JSONEachRow{"id":"1", "name":"java"};

使用指定格式导出:

clickhouse-client -h bigdata04 --port 9977 --database="dylan" --query="select * from dylan.dylan_test1  FORMAT CSV" > dylan_test1.csv;

清空 dylan_test1 表:

truncate dylan_test1;

使用指定格式导入:

clickhouse-client -h bigdata04 --port 9977 --database="dylan" --query="insert into dylan.dylan_test1 FORMAT CSV" < ./dylan_test1.csv

2.2. update 和 delete

ClickHouse 提供了 DELETE 和 UPDATE 的能力,这类操作被称为 Mutation 查询,它可以看作ALTER 语句的变种。虽然Mutation能最终实现修改和删除,但不能完全以通常意义上的 UPDATE 和 DELETE 来理解,我们必须清醒地认识到它的不同:

总结:

1、Mutation操作适用于批量数据的修改和删除
2、不支持事务:一旦语句被提交执行就会立刻对现有的数据产生影响,无法回滚。
3、Mutation 操作执行是一个异步的过程,语句提交会立即返回,但是不代表具体逻辑已经执行完毕,具体的执行记录需要在system.mutations 系统表查询。

delete 的完整语法:

ALTER TABLE [db_name.]table_name DELETE WHERE filter_expr

查看数据目录:

[root@bigdata04 data]# pwd
/var/lib/clickhouse/data/dylan/data
[root@bigdata04 data]# ll
total 24
drwxr-x--- 2 root root 4096 Aug  8 15:34 202104_1_1_0
drwxr-x--- 2 root root 4096 Aug  8 15:35 202104_1_1_0_2
drwxr-x--- 2 root root 4096 Aug  8 15:35 202104_1_1_0_3
drwxr-x--- 2 root root    6 Aug  8 15:34 detached
-rw-r----- 1 root root    1 Aug  8 15:34 format_version.txt
-rw-r----- 1 root root  109 Aug  8 15:35 mutation_2.txt
-rw-r----- 1 root root   96 Aug  8 15:35 mutation_3.txt

可以发现在执行了update,delete操作之后数据目录会生成文件mutation_2.txt,mutation_3.txt。此 外还有在同名的目录下在末尾增加了_2 ,_3的后缀。可以看到 mutation_2.txt 和 mutation_3.txt 是日 志文件,完整的记录了update 和 delete 操作语句和时间。

mutation_id:生成对应的日志文件用于记录相关的信息。数据删除的过程是以数据表的每个分区目录为单位,将所有目录重写为新的目录,在目录的命名规则是在原有的名称上加上 block_numbers.number。数据的在重写的过程中会将所需要删除的数据去掉。旧的数据并不会立即删除,而是被标记为非激活状态(active = 0),等到MergeTree 引擎的下一次合并动作触发的时候,这些非活动目录才会被真正的从物理上删除。

UPDATE 语句不能修改分区键和主键。


赞(1) 更多分享

上篇: 使用Canal实现ClickHouse实时同步MySQL数据
下篇: MySQL按月自动创建分区表(千万级大表优化)