目录
Clickhouse
表引擎
库表创建&查询
建库
建表
1、 本地表与分布式表
2、创建本地表
3、 创建分布式表
常用数据类型
DDL变更
ALTER语法
数据查询
Distinct子句
From子句
Sample子句
Join子句
执行计划
实践
Clickhouse是俄罗斯互联网公司Yandex开发并开源的一个用于联机分析(OLAP)的列式数据库管理系统(DBMS),使用C++语言开发。
特点(截止20.12.3.3版本):
真正的面向列的数据库
数据在磁盘储存,支持数据压缩
分布式,支持在多个服务器上部署集群,支持分片、副本、数据复制等功能,无主从架构
SQL语法支持 (支持大部分标准SQL)
多核并行处理,动态代码生成,向量化引擎
支持实时数据摄入
支持索引 (只支持主键索引,为稀疏索引[注1] )
低延迟,查询响应非常快,适合不变数据 (如海量实时用户行为数据、点击日志等)在线查询
支持近似预估计算,支持数据抽样[注2]
支持针对账号限制查询复杂性以及配额[注3]
不完美的地方:
不支持事务
旧版本不支持update,delete 新版本:(异步执行,不能及时生效)
alter table table1 update filed1 = value1, field2 = value2 where condition;
alter table table1 delete where condition;
不支持相关子查询[注4]
Join写法有些独特,分布式表需要加global【可通过配置文件修改成不需要加[注5] 】
分片不能自动扩展,需要人为修改配置[注6]
分布式不是完全自动透明的[注7]
UDF支持不方便[注8] (需要手动编写C++代码实现udf函数)
主键不唯一,导致增量更新的困难,不方便按照主键去重/覆盖更新
注意:
使用的ck版本不支持删除操作,只能更新
Clickhouse创建本地表的时候需要指定表引擎,类似于mysql可以选择innodb或者myisam,不同的表引擎具有不同特性,发挥不同功能,需要根据具体业务场景选择。
日常业务中使用最多的就是MergeTree系列表引擎,如下:
引擎名称 | 引擎说明 | 场景 | 备注 |
---|---|---|---|
MergeTree | 合并树 | 非去重普通场景 | 最基础的引擎,底层类似于LSM的方式,其他引擎都是基于其增加一些特殊特性 |
ReplacingMergeTree | 去重合并树 | 需要按主键去重/覆盖更新的场合 | 比较通用的引擎,支持同主键去重,但是去重是后台执行,不可控(实测非常慢) |
CollapsingMergeTree | 折叠树 | 也是为了去重,方式比较独特 | 如果按照主键去重,需要在写入新数据时知道原来的数据方可 |
VersionedCollapsingMergeTree | 版本折叠树 | 折叠树基础上加了一些优化 | 折叠树基础上增加了版本的概念,按版本去重 |
SummingMergeTree | 求和合并树 | 相同主键的数据合并成一行并对指标自动求和 | 推荐结合MergeTree使用,MergeTree存放详细数据,SummmingMergeTree存储聚合数据 |
AggregatingMergeTree | 聚集合并树 | 相同主键的数据合并成一行并对指标自动聚集计算 | 是SummingMergeTree的扩展,不局限于sum而是可以有其他聚合逻辑,常配合物化视图做增量数据的聚合 |
以上表引擎为单副本情况下,如果使用多副本需要使用Replicated*系列的引擎,这样可以配合zookeeper实现副本数据复制
引擎名称 | 说明 |
---|---|
ReplicatedMergeTree | 与对应的不带Replicated系列的功能一致,只是这种引擎支持副本数据复制 |
ReplicatedReplacingMergeTree | |
ReplicatedCollapsingMergeTree | |
ReplicatedVersionedCollapsingMergeTree | |
ReplicatedSummingMergeTree | |
ReplicatedAggregatingMergeTree |
分布式表引擎:Distributed,用于创建分布式表
--建库语法:
create database if not exists 库名
on cluster 集群名
engine = Ordinary; //engine就固定用这个
--例:
create database if not exists saas_aries
on cluster default_cluster
engine = Ordinary;
--使用库:
USE saas_aries;
Clickhouse中的表有本地表和分布式表的概念:
本地表指各个分片节点自身的表,在各个分片节点上分别存储各自的数据,在不同的分片节点上查询本地表也只会展示当前节点上的数据;
分布式表需要关联到本地表,本身不存储数据,实际上相当于一张分布式视图,在任意节点查询分布式表,clickhouse会把计算分发到各分片节点,汇总各节点的计算结果后返回全局结果
例如:
我们有3个分片节点,有一张本地表t_data_local,共100W数据,那么节点1可能存储30W数据,节点2有20W数据,节点3有50W数据;另外有一张分布式表t_data关联到t_data_local
在节点1上查询select count(*) from t_data_local,数据量为30W;节点2上查询为20W,节点3为50W
而不管在哪个节点查询select count(*) from t_data,均为100W数据
所以查询需要查询分布式表才能获取完整的数据
Clickhouse中建表步骤分为两步:1)建立本地表;2)建立分布式表,
建议规范:分布式表使用正常的表名,本地表在后面加_local以示区分
--建表语法:
CREATE TABLE IF NOT EXISTS 库名.本地表名
ON CLUSTER 集群名
(
字段1 类型 [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1], //clickhouse也支持列的ttl,过期后这一列数据删除
字段2 类型 [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],
...
INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = 某引擎
ORDER BY (排序键)
[PARTITION BY expr] //分区键
[PRIMARY KEY expr] //主键,一般不设,默认跟排序键相同
[SAMPLE BY expr] //Clickhouse支持抽样
[TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...] //ttl,类似cassandra,clickhouse支持数据行的ttl,过期后自动删除数据
[SETTINGS name=value, ...] //一些表的设置项
--以ReplicatedReplacingMergeTree为例:
create table if not exists saas_aries.dim_ec_store_localon cluster default_cluster(
`pid` String comment '商家ID'
,`store_id` String comment '所属门店ID'
,`store_name` String comment '所属门店名称'
,`is_head_store` String
,`create_time` DateTime comment '创建时间'
)
engine = ReplicatedReplacingMergeTree(
'/clickhouse/tables/{shard}/saas_aries/dim_ec_store_local', '{replica}'
)
order by (pid, store_id)
settings index_granularity = 8192;
建好本地表后,需要建立分布式表关联本地表
--建表语法(使用Distributed引擎):
create table if not exists 库名.分布式表名on cluster 集群名as 库名.本地表名engine = Distributed(集群名, 库名, 本地表名, 分布式规则);
--例:
create table if not exists saas_aries.dim_ec_storeon cluster default_clusteras db_test.dim_ec_store_localengine = Distributed(default_cluster, saas_aries, dim_ec_store_local, rand());
--//rand表示随机分布,也可以定义其他分布策略,如hash函数等
备注:所有的DDL语句务必都加上on cluster 集群名,这样在任意一个节点执行就相当于在集群所有节点执行,不然就需要到每个节点上依次执行一遍这个语句
分类 | 常用类型 | 说明 | |
---|---|---|---|
整数 | 有符号整数 | Int8 | 数据范围: -128 ~ 127 |
Int16 | 数据范围:-32768 ~ 32767 | ||
Int32 | 数据范围: -2147483648 ~ 2147483647 | ||
Int64 | 数据范围: -9223372036854775808 ~ 9223372036854775807 | ||
无符号整数 | UInt8 | 数据范围: 0 ~ 255,clickhouse中没有0-1布尔类型,用这个类型代替 | |
UInt16 | 数据范围: 0 ~ 65535 | ||
UInt32 | 数据范围: 0 ~ 4294967295 | ||
UInt64 | 数据范围: 0 ~ 18446744073709551615 | ||
浮点数 | Float32 | 32位浮点数 | |
Float64 | 64位浮点数 | ||
定点数 | Decimal32(S) | 32位定点数,数据范围: -1 * 10^(9-S) ~ 1 * 10^(9-S) | |
Decimal64(S) | 64位定点数,数据范围: -1 * 10^(18-S) ~ 1 * 10^(18-S) | ||
字符串 | 不定长 | String | 字符串,任意长度 |
定长 | FixedString(N) | 固定长度N的字符串,N为正整数 | |
日期与时间 | 日期 | Date | 日期类型,'2021-01-08',精确到天 |
时间 | DateTime | 日期时间类型,'2021-01-08 13:13:13',精确到秒 | |
DateTime64(precision, [timezomne]) | 日期时间类型,可指定精度和时区(可选),如DateTime64(3)可表示'2021-01-08 13:13:13.222' | ||
枚举类型 | Enum8 | 用法:CREATE TABLE t_test_local( field1 Enum8('hello' = 1, 'world' = 2));field1字段被设置为枚举类型,它只能存储两种值'hello'与'world',插入其他值会报错误 | |
Enum16 | 同Enum8,只是整数范围为Int16,一般没有必要,Enum8已足够了 | ||
特殊:Nullable | Nullable(数据类型) | 解释:Clickhouse中的字段类型与Mysql不同,默认来说是不允许为空的(基于性能考虑),会自动赋予个默认值如果需要允许为空的情况出现,可以用Nullable,如下所示CREATE TABLE t_test_local( field1 Nullable(Int32));表示field1字段是Int32类型,但是允许为空 |
需要先在本地表操作,再在分布式表同样操作(所有的表结构变更语句都需要先在本地表执行,然后在分布式表执行,都执行完毕才可以)
ALTER TABLE 库名.表名 ON CLUSTER 集群名 ADD|DROP|CLEAR|COMMENT|MODIFY COLUMN ...
新增字段
ALTER TABLE 库名.表名 ON CLUSTER 集群名 ADD COLUMN IF NOT EXISTS 字段名 类型 [默认值] AFTER 已存在的字段;
--先在本地表新增:
ALTER TABLE db_test.t_test_local ON CLUSTER default_cluster ADD COLUMN IF NOT EXISTS field5 Int32 default 0 AFTER field4;
--然后在分布式表新增:
ALTER TABLE db_test.t_test ON CLUSTER default_cluster ADD COLUMN IF NOT EXISTS field5 Int32 default 0 AFTER field4;
删除字段
ALTER TABLE 库名.表名 ON CLUSTER 集群名 DROP COLUMN IF EXISTS 字段名;
--先在本地表删除:
ALTER TABLE db_test.t_test_local ON CLUSTER default_cluster DROP COLUMN IF EXISTS field5;
--然后在分布式表删除:
ALTER TABLE db_test.t_test ON CLUSTER default_cluster DROP COLUMN IF EXISTS field5;
修改字段
ALTER TABLE 库名.表名 ON CLUSTER 集群名 MODIFY COLUMN IF EXISTS 字段名 类型 [默认值] [TTL];
--//修改字段可以修改字段的类型、字段的默认值和字段的TTL,注意:不支持修改字段名称!
--先在本地表修改:
ALTER TABLE db_test.t_test_local ON CLUSTER default_cluster MODITY COLUMN IF EXISTS field5 Int64 default 1;
--//把field5的类型改为Int64,默认值改为1
--然后在分布式表修改:
ALTER TABLE db_test.t_test ON CLUSTER default_cluster MODITY COLUMN IF EXISTS field5 Int64 default 1;
--//把field5的类型改为Int64,默认值改为1
设置注释
ALTER TABLE 库名.表名 ON CLUSTER 集群名 COMMENT COLUMN IF EXISTS 字段名 注释;
--//设置字段的注释
--先在本地表设置:
ALTER TABLE db_test.t_test_local ON CLUSTER default_cluster COMMENT COLUMN IF EXISTS field5 'aaa'; //把field5的注释设置为aaa
--然后在分布式表设置:
ALTER TABLE db_test.t_test ON CLUSTER default_cluster COMMENT COLUMN IF EXISTS field5 'aaa'; //把field5的注释设置为aaa
清空列
这里例外,这个只在本地表执行,不需要在分布式表执行:因为这个实际是操作值,而分布式表是一个结构视图,本地表的值变化了自动会反映到分布式表
ALTER TABLE 库名.表名 ON CLUSTER 集群名 CLEAR COLUMN IF EXISTS 字段名 [IN PARTITION 分区名];
--//清空指定列的值,in partition 可选,如果设置,只清空在改分区下的指定列的值
--在本地表清空:
ALTER TABLE db_test.t_test_local ON CLUSTER default_cluster CLEAR COLUMN IF EXISTS field5;
--//把field5的值清空(会按照默认值填充,比如0或者null)
distinct 用于在结果集总去除重复项,只保留一行
如果count distinct,表示去重计数
可以使用uniq代替count distinct,为不精准去重计数,但是速度会快几倍
select count(distinct pid) from t_table1; => 结果528329 (精确) select uniq(pid) from t_table1; => 结果528210; (基于算法,不精确)
From子句可以从表/视图/子查询/表函数中查询数据
From子句可以附带final修饰符,当 FINAL 修饰符被指定,ClickHouse会在返回结果之前完全合并数据,从而执行给定表引擎合并期间发生的所有数据转换。
select * from t_table1; => t_table1中相同主键的记录不会合并,如果表中有重复行,会出现多条 select * from t_table1 final; =>t_table1中相同主键的记录会合并,如果表中有重复行,只会出现一条
但是,使用的查询 FINAL 执行速度会变慢很多,在绝大多数情况下,避免使用 FINAL.
可选的方法是
1)首先使用ReplacingMergeTree引擎,可以后台合并相同主键记录(非即时、不可控)
2) 在系统相对空闲时间执行optimize table,手动合并
3)在应用系统处理
Sample子句是Clickhouse支持数据抽样功能,进行近似查询。
启用数据采样时,不会对所有数据执行查询,而只对特定部分数据(样本)执行查询。 近似查询处理在以下情况下可能很有用:
当你有严格的查询时间需求(如<100ms),但你不能通过额外的硬件资源来满足他们的成本。
当您的原始数据不准确时,所以近似不会明显降低质量。
业务需求的目标是近似结果,对准确性要求不太高。
要实现Sample抽样,首先需要在该表的建表语句中设置抽样表达式,具体查看建表相关文档。
Sample语句的语法表达式为
SAMPLE k, 其中k是一个0-1之间的小数,比如k=0.2,表示抽样20%数据来预估
--例如:select avg(cost_price) from t_table1 sample 0.2; --// clickhouse会抽样20%的数据样本,计算平均值来预估总体平均值
Clickhouse 支持 inner join / left join / right join / full join / cross join
Clickhouse的分布式表Join子句,必须要在前面加上global关键字
select a.*, b.* from a left join b on a.field1 = b.field1; --//错误 select a.*, b.* from a global left join b on a.field1 = b.field1; --//正确
Clickhouse数据库支持查看执行计划,具体方式为:explain + sql;
explain select * from saas_aries.dim_ec_store_local where pid = 2847 limit 100;
Dbeaver客户端软件最新版(21.0.2以上)已经可以支持查看执行计划
注意:
目前clickhouse的执行计划查询功能还不是很完善,显示的信息比较少
查看执行要查询本地表,不要查询分布式表,如果查询分布式表,目前执行计划只会给出一行信息,如下所示
《Doris实时数仓实战》