ClickHouse介绍(四)ClickHouse使用操作
ClickHouse使用操作
这章主要介绍在ClickHouse使用的各个操作的注意点。常规的统一语法不做详细介绍。
1. Join操作
在ClickHouse中,对连接操作定义了不同的精度,包含ALL、ANY和ASOF三种类型,默认为ALL。可以通过join_default_strictness配置修改默认精度(位于system.setting表中)。下面分别说明这3种精度。
首先建表并插入测试数据:
--表join_tb1
CREATE TABLE join_tb1
(
`id` String,
`name` String,
`time` DateTime
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(time)
ORDER BY id --表 join_tb2
CREATE TABLE join_tb2
(
`id` String,
`rate` UInt8,
`time` DateTime
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(time)
ORDER BY id --表 join_tb3
CREATE TABLE join_tb3
(
`id` String,
`star` UInt8
)
ENGINE = MergeTree
ORDER BY id --插入数据
INSERT INTO join_tb1 VALUES
('1', 'ClickHouse', '2019-05-01 12:00:00')
('2', 'Spark', '2019-05-01 12:30:00')
('3', 'ElasticSearch', '2019-05-01 13:00:00')
('4', 'HBase', '2019-05-01 13:30:00')
(NULL, 'ClickHouse', '2019-05-01 14:00:00')
(NULL, 'Spark', '2019-05-01 14:30:00') INSERT INTO join_tb2 VALUES
('1', 100, '2019-05-01 11:55:00')
('1', 105, '2019-05-01 11:50:00')
('2', 90, '2019-05-01 12:01:00')
('3', 80, '2019-05-01 13:10:00')
('5', 70, '2019-05-01 14:00:00')
('6', 60, '2019-05-01 13:55:00') INSERT INTO join_tb3 VALUES
('1', 1000)
('2', 900)
1.1. ALL
如果左表内的一行数据,在右表中有多行数据与之连接匹配,则返回右表种全部连接的数据。连接依据为:left.key=right.key。
SELECT a.id, a.name, b.rate FROM join_tb1 AS a ALL INNER JOIN join_tb2 AS b ON a.id=b.id SELECT
a.id,
a.name,
b.rate
FROM join_tb1 AS a
ALL INNER JOIN join_tb2 AS b ON a.id = b.id ┌─id─┬─name──────────┬─rate─┐
│ 1 │ ClickHouse │ 100 │
│ 1 │ ClickHouse │ 105 │
│ 2 │ Spark │ 90 │
│ 3 │ ElasticSearch │ 80 │
└────┴───────────────┴──────┘
1.2. ANY
如果左表内的一行数据,在右表中有多行数据与之连接匹配,则仅返回右表中第一行连接的数据。连接依据同样为:left.key=right.key
SELECT
a.id,
a.name,
b.rate
FROM join_tb1 AS a
ANY INNER JOIN join_tb2 AS b ON a.id = b.id ┌─id─┬─name──────────┬─rate─┐
│ 1 │ ClickHouse │ 100 │
│ 2 │ Spark │ 90 │
│ 3 │ ElasticSearch │ 80 │
└────┴───────────────┴──────┘
1.3. ASOF
ASOF 是一种模糊连接,允许在连接键之后追加定义一个模糊连接的匹配条件asof_column,例如:
SELECT
a.id,
a.name,
b.rate,
a.time,
b.time
FROM join_tb1 AS a
ASOF INNER JOIN join_tb2 AS b ON (a.id = b.id) AND (a.time >= b.time) ┌─id─┬─name───────┬─rate─┬────────────────time─┬──────────────b.time─┐
│ 1 │ ClickHouse │ 100 │ 2019-05-01 12:00:00 │ 2019-05-01 11:55:00 │
│ 2 │ Spark │ 90 │ 2019-05-01 12:30:00 │ 2019-05-01 12:01:00 │
└────┴────────────┴──────┴─────────────────────┴─────────────────────┘
根据官网介绍的语法:
SELECT expressions_list
FROM table_1
ASOF LEFT JOIN table_2
ON equi_cond AND closest_match_cond
https://clickhouse.tech/docs/en/sql-reference/statements/select/join/
ASOF会先以 left.key = right.key 进行连接匹配,然后根据AND 后面的 closest_match_cond(也就是这里的a.time >= b.time)过滤出最符合此条件的第一行连接匹配的数据。
另一种写法是使用USING,语法为:
SELECT expressions_list
FROM table_1
ASOF JOIN table_2
USING (equi_column1, ... equi_columnN, asof_column)
举例:
SELECT
a.id,
a.name,
b.rate,
a.time,
b.time
FROM join_tb1 AS a
ASOF INNER JOIN join_tb2 AS b USING (id, time) Query id: 075f7e4a-7355-4e11-ae3b-0e3275912a3e ┌─id─┬─name───────┬─rate─┬────────────────time─┬──────────────b.time─┐
│ 1 │ ClickHouse │ 100 │ 2019-05-01 12:00:00 │ 2019-05-01 11:55:00 │
│ 2 │ Spark │ 90 │ 2019-05-01 12:30:00 │ 2019-05-01 12:01:00 │
└────┴────────────┴──────┴─────────────────────┴─────────────────────┘
对 asof_colum 字段的使用有2点需要注意:
- asof_column 必须是整型、浮点型和日期型这类有序序列的数据类型
- asof_column不能是数据表内的唯一字段,也就是说连接键(JOIN KEY)和asof_column不能是同一字段
1.4. Join性能
在执行JOIN时,ClickHouse对执行的顺序没有特别优化,JOIN操作会在WHERE以及聚合查询前运行。
JOIN操作结果不会缓存,所以每次JOIN操作都会生成一个全新的执行计划。如果应用程序会大量使用JOIN,则需进一步考虑借助上层应用侧的缓存服务或使用JOIN表引擎来改善性能(JOIN表引擎不支持ASOF精度)。JOIN表引擎会在内存中保存JOIN结果。
在某些情况下,IN的效率比JOIN要高。
在使用JOIN连接维度表时,JOIN操作可能并不会特别高效,因为右则表对每个query来说,都需要加载一次。在这种情况下,外部字典(external dictionaries)的功能会比JOIN性能更好。
1.5. JOIN的内存限制
默认情况下,ClickHouse使用Hash Join 算法。它会将右侧表(right_table)加载到内存,并为它创建一个hash table。在达到了内存使用的一个阈值后,ClickHouse会转而使用Merge Join 算法。
可以通过以下参数限制JOIN操作消耗的内存:
- max_rows_in_join:限制hash table中的行数
- max_bytes_in_join:限制hash table的大小
在达到任何上述limit后,ClickHouse会以join_overflow_mode 的参数进行动作。此参数包含2个可选值:
- THROW:抛出异常并终止操作
- BREAK:终止操作但并不抛出异常
2. WHERE与PREWHERE子句
WHERE可以通过表达式来过滤数据,如果过滤条件恰好为主键字段,则可以进一步借助索引加速查询,所以WHERE子句是决定查询语句是否能使用索引的判断依据(前提是表引擎支持索引)。
除此之外,ClickHouse还提供了PREWHERE子句用于条件过滤,它可以更有效地进行过滤优化,仅用于MergeTree表系列引擎。
PREWHERE与WHERE不同之处在于:使用PREWHERE时,首先只会去PREWHERE指定的列字段数据,用于数据过滤的条件判断。在数据过滤之后再读取SELECT声明的列字段以补全其余属性。所以在一些场合下,PREWHERE相比WHERE而言,处理的数据更少,性能更高。
默认情况下,即使在PREWHERE子句没有显示指定的情况下,它也会自动移动到WHERE条件到PREWHERE阶段。
下面做个对比:
# 默认自动开启了PREWHERE,查询速度为:
select WatchID, Title, GoodEvent from hits_v1 where JavaEnable=1; …
6535088 rows in set. Elapsed: 1.428 sec. Processed 8.87 million rows, 863.90 MB (6.21 million rows/s., 604.82 MB/s.) # 关闭PREWHERE
set optimize_move_to_prewhere=0 # 关闭自动PREWHERE,查询速度为
6535088 rows in set. Elapsed: 1.742 sec. Processed 8.87 million rows, 864.55 MB (5.09 million rows/s., 496.20 MB/s.)
可以看到2条语句处理的数据总量没有变化,但是其数据处理量稍有降低(PREWHERE为863.90MB),且每秒吞吐量上升(PREWHER为604.82MB/s,WHERE为496.20MB/s)。
对比2条语句的执行计划:
# PREWHERE
explain select WatchID, Title, GoodEvent from hits_v1 prewhere JavaEnable=1; EXPLAIN
SELECT
WatchID,
Title,
GoodEvent
FROM hits_v1
PREWHERE JavaEnable = 1 Query id: 103fd24a-e718-4304-9f75-4900528c1d1a ┌─explain───────────────────────────────────────────────────────────────────┐
│ Expression ((Projection + Before ORDER BY)) │
│ SettingQuotaAndLimits (Set limits and quota after reading from storage) │
│ ReadFromStorage (MergeTree) │
└───────────────────────────────────────────────────────────────────────────┘ # WHERE
explain select WatchID, Title, GoodEvent from hits_v1 where JavaEnable=1; EXPLAIN
SELECT
WatchID,
Title,
GoodEvent
FROM hits_v1
WHERE JavaEnable = 1 Query id: 9b470524-1320-4e9f-bade-cf8c2c9944c8 ┌─explain─────────────────────────────────────────────────────────────────────┐
│ Expression ((Projection + Before ORDER BY)) │
│ Filter (WHERE) │
│ SettingQuotaAndLimits (Set limits and quota after reading from storage) │
│ ReadFromStorage (MergeTree) │
└─────────────────────────────────────────────────────────────────────────────┘
可以看到相比WHERE语句,PREWHERE语句的执行计划省去了一次Filter操作。
3. Group By
Group By的用法非常常见,ClickHouse中执行聚合查询时,若是SELECT后面只声明了聚合函数,则GROUP BY 关键字可以省略:
SELECT
SUM(data_compressed_bytes) AS compressed,
SUM(data_uncompressed_bytes) AS uncompressed
FROM system.parts Query id: e38e3ec1-968d-4442-ba7d-b8555f27e0d0 ┌─compressed─┬─uncompressed─┐
│ 1851073942 │ 9445387666 │
└────────────┴──────────────┘
聚合查询还能配合WITH ROLLUP、WITH CUBE和WITH TOTALS三种修饰符获取额外的汇总信息。
3.1. WITH ROLLUP
ROLLUP便是上卷数据,按聚合键从右到左,基于聚合函数依次生成分组小计和总计。如果设聚合键的个数为n,则最终会生成小计的个数为n+1。例如:
SELECT
table,
name,
SUM(bytes_on_disk)
FROM system.parts
GROUP BY
table,
name
WITH ROLLUP
ORDER BY table ASC ┌─table──────────────────────────────────────────┬─name───────────────────────────────────┬─SUM(bytes_on_disk)─┐
│ │ │ 1857739143 │
│ .inner_id.604be4d8-bb5c-437b-ada9-3d3d5a91fc24 │ │ 638 │
│ .inner_id.604be4d8-bb5c-437b-ada9-3d3d5a91fc24 │ 953e60a1e8747360786c2b70a223788d_2_4_1 │ 318 │
│ .inner_id.604be4d8-bb5c-437b-ada9-3d3d5a91fc24 │ acb795a12c7ba41b0ed4c3d94a008ecd_1_3_1 │ 320 │
│ agg_table │ │ 358 │
│ agg_table │ 201909_2_2_0 │ 358 │
可以看到第1行是一个汇总,统计的SUM(bytes_on_disk)的总行数。而每个table字段都有一个汇总(例如.inner_id.604be4d8-bb5c-437b-ada9-3d3d5a91fc24 表第一行以及agg_table 第一行)。
3.2. WITH CUBE
CUBE也是数仓里重要的概念,基于聚合键之间所有的组合生成统计信息。如果聚合键的个数为n,则最终聚合数据的个数为2的n次方。例如:
--建表
CREATE TABLE person
(
`id` int,
`name` String,
`course` String,
`year` DateTime,
`points` int
)
ENGINE = MergeTree
ORDER BY id --插入数据
INSERT INTO person VALUES
(1, 'jane', 'CS', '2021-01-02 11:00:00', 50),
(2, 'tom', 'CS', '2021-01-03 11:00:00', 60),
(3, 'bob', 'BS', '2021-01-03 11:00:00', 50),
(4, 'alice', 'BS', '2021-01-01 11:00:00', 40),
(5, 'jane', 'ACC', '2021-01-02 11:00:00', 70),
(6, 'bob', 'ACC', '2021-01-03 11:00:00', 90),
(7, 'jane', 'MATH', '2021-01-04 11:00:00', 100) --Cube计算
SELECT
name,
course,
year,
AVG(points)
FROM person
GROUP BY
name,
course,
year
WITH CUBE ┌─name──┬─course─┬────────────────year─┬─AVG(points)─┐
│ jane │ ACC │ 2021-01-02 11:00:00 │ 70 │
│ bob │ ACC │ 2021-01-03 11:00:00 │ 90 │
│ alice │ BS │ 2021-01-01 11:00:00 │ 40 │
… ┌─name─┬─course─┬────────────────year─┬───────AVG(points)─┐
│ │ │ 2021-01-01 11:00:00 │ 40 │
│ │ │ 2021-01-03 11:00:00 │ 66.66666666666667 │
│ │ │ 2021-01-02 11:00:00 │ 60 │
│ │ │ 2021-01-04 11:00:00 │ 100 │
└──────┴────────┴─────────────────────┴───────────────────┘
┌─name─┬─course─┬────────────────year─┬───────AVG(points)─┐
│ │ │ 1970-01-01 00:00:00 │ 65.71428571428571 │
└──────┴────────┴─────────────────────┴───────────────────┘
可以看到结果中会生成 8 个统计结果(部分结果已省略)。
3.3. WITH TOTALS
WITH TOTALS会基于聚合函数对所有数据进行统计(比原结果多一行总的统计结果),例如:
SELECT
name,
course,
year,
AVG(points)
FROM person
GROUP BY
name,
course,
year
WITH TOTALS ┌─name──┬─course─┬────────────────year─┬─AVG(points)─┐
│ jane │ ACC │ 2021-01-02 11:00:00 │ 70 │
│ bob │ ACC │ 2021-01-03 11:00:00 │ 90 │
│ alice │ BS │ 2021-01-01 11:00:00 │ 40 │
│ jane │ CS │ 2021-01-02 11:00:00 │ 50 │
│ jane │ MATH │ 2021-01-04 11:00:00 │ 100 │
│ tom │ CS │ 2021-01-03 11:00:00 │ 60 │
│ bob │ BS │ 2021-01-03 11:00:00 │ 50 │
└───────┴────────┴─────────────────────┴─────────────┘ Totals:
┌─name─┬─course─┬────────────────year─┬───────AVG(points)─┐
│ │ │ 1970-01-01 00:00:00 │ 65.71428571428571 │
└──────┴────────┴─────────────────────┴───────────────────┘
4. 查看SQL执行计划
ClickHouse目前并没有直接提供EXPLAIN的详细查询计划,当前EXPLAIN仅是输出一个简单的计划。不过我们仍可以借助后台服务日志来实现此功能,例如执行以下语句即可看到详细的执行计划:
clickhouse-client --password xxx --send_logs_level=trace <<<'select * from tutorial.hits_v1' > /dev/null
打印信息如下(仅截取关键信息):
tutorial.hits_v1 (SelectExecutor): Key condition: unknown
=> 查询未使用主键索引 tutorial.hits_v1 (SelectExecutor): MinMax index condition: unknown
=> 未使用分区索引 tutorial.hits_v1 (SelectExecutor): Not using primary index on part 201403_1_29_2
=> 未在分区 201403_1_29_2 下使用primary index tutorial.hits_v1 (SelectExecutor): Selected 1 parts by partition key, 1 parts by primary key, 1094 marks by primary key, 1094 marks to read from 1 ranges
=> 选择了1个分区,共计1094个marks executeQuery: Read 8873898 rows, 7.88 GiB in 21.9554721 sec., 404177 rows/sec., 367.50 MiB/sec.
=> 读取 8873898条数据,7.88G 数据,耗时21.955秒… MemoryTracker: Peak memory usage (for query): 361.67 MiB.
=> 消耗内存量
下面优化一下查询:
clickhouse-client --password xxx --send_logs_level=trace <<<"select WatchID from tutorial.hits_v1 where EventDate='2014-03-17'" > /dev/null
打印结果为:
InterpreterSelectQuery: MergeTreeWhereOptimizer: condition "EventDate = '2014-03-17'" moved to PREWHERE
=> 自动调用了PREWHERE tutorial.hits_v1 (SelectExecutor): Key condition: (column 1 in [16146, 16146])
=> 使用了主键索引 tutorial.hits_v1 (SelectExecutor): MinMax index condition: (column 0 in [16146, 16146])
=> 使用了分区索引 tutorial.hits_v1 (SelectExecutor): Selected 1 parts by partition key, 1 parts by primary key, 755 marks by primary key, 755 marks to read from 64 ranges
=> 根据分区键选择了一个分区 executeQuery: Read 6102294 rows, 58.19 MiB in 0.032661599 sec., 186833902 rows/sec., 1.74 GiB/sec.
=> 读到的数据,以及速度 MemoryTracker: Peak memory usage (for query): 11.94 MiB.
=> 消耗内存量
总的来说,ClickHouse未直接通过EXPLAIN语句提供查看语句执行的详细过程,但是可以变相的将日志设置到DEBUG或是TRACE级别,实现此功能,并分析SQL的执行日志。
ClickHouse介绍(四)ClickHouse使用操作的更多相关文章
- HTML 事件(四) 模拟事件操作
本篇主要介绍HTML DOM中事件的模拟操作. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流与事件委托 4. ...
- 从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式
本系列文章导航 从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式 一.摘要 本篇文章讲解如何使用jQuery获取和操作元素的属性和CSS样式. 其中DOM属性和元素属性的区分值得 ...
- Lucene.Net 2.3.1开发介绍 —— 四、搜索(一)
原文:Lucene.Net 2.3.1开发介绍 -- 四.搜索(一) 既然是内容筛选,或者说是搜索引擎,有索引,必然要有搜索.搜索虽然与索引有关,那也只是与索引后的文件有关,和索引的程序是无关的,因此 ...
- Java Spring Boot VS .NetCore (四)数据库操作 Spring Data JPA vs EFCore
Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...
- {MySQL数据库初识}一 数据库概述 二 MySQL介绍 三 MySQL的下载安装、简单应用及目录介绍 四 root用户密码设置及忘记密码的解决方案 五 修改字符集编码 六 初识sql语句
MySQL数据库初识 MySQL数据库 本节目录 一 数据库概述 二 MySQL介绍 三 MySQL的下载安装.简单应用及目录介绍 四 root用户密码设置及忘记密码的解决方案 五 修改字符集编码 六 ...
- 孤荷凌寒自学python第四十四天Python操作 数据库之准备工作
孤荷凌寒自学python第四十四天Python操作数据库之准备工作 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 今天非常激动地开始接触Python的数据库操作的学习了,数据库是系统化设计 ...
- Html5 学习系列(四)文件操作API
原文:Html5 学习系列(四)文件操作API 引言 在之前我们操作本地文件都是使用flash.silverlight或者第三方的activeX插件等技术,由于使用了这些技术后就很难进行跨平台.或者跨 ...
- X-Cart 学习笔记(四)常见操作
目录 X-Cart 学习笔记(一)了解和安装X-Cart X-Cart 学习笔记(二)X-Cart框架1 X-Cart 学习笔记(三)X-Cart框架2 X-Cart 学习笔记(四)常见操作 五.常见 ...
- Lucene.Net 2.3.1开发介绍 —— 四、搜索(三)
原文:Lucene.Net 2.3.1开发介绍 -- 四.搜索(三) Lucene有表达式就有运算符,而运算符使用起来确实很方便,但另外一个问题来了. 代码 4.3.4.1 Analyzer anal ...
- Lucene.Net 2.3.1开发介绍 —— 四、搜索(二)
原文:Lucene.Net 2.3.1开发介绍 -- 四.搜索(二) 4.3 表达式用户搜索,只会输入一个或几个词,也可能是一句话.输入的语句是如何变成搜索条件的上一篇已经略有提及. 4.3.1 观察 ...
随机推荐
- EPAI手绘建模APP介绍
本软件是一个基于OpenCASCADE.android JNI开发的APP.底层用c++实现,UI层用android实现.底层和UI层之间通过JNI接口和json数据格式通信. ...
- 基于三菱Q系列cc-Link的立体仓库控制系统
系统说明: 方案选择: 工艺流程: 触摸屏设计: 程序设计:采用SFC进行编程,结构清晰,逻辑明了 本文章为学习记录,水平有限,望各路大佬们轻喷!!! 转载请注明出处!!!
- web页面打开直接调用vlc播放视频
简介 大家都知道现在我们在网页所播放的视频都是h264编码格式,可以供所有设备正常播放.然而,相比h265它的体积更大.质量更差.目前h265大多应用于安防,体积小可以更好的存储,不过它也有着缺点,成 ...
- windows远程桌面和远程协助有什么区别
一言以蔽之: windows远程桌面是被动式的,只要你开启了这个功能选项,对方有地址和密码就能使用这台计算机. windows远程协助是主动式的,需要向对方提出协助请求,对方答应后即可登录电脑,协助操 ...
- 扩展Unity编辑器顶部Toolbar,增加自定义按钮
游戏需要增加几种启动模式,要在编辑器顶部Toolbar处增加几个按钮:进行下扩展. 这部分Unity没有直接提供接口,需通过反射实现.看了下有一个开源库: https://github.com/mar ...
- 化繁为简|AIRIOT智慧水务信息化建设解决方案
"生产自动化,管理信息化"是现代化水厂建设的目标之一,需要在水质要求.工艺.生产.管理.环境等监测方面达到精细化管理标准,这是一个高度智能化,实现化繁为简智慧进阶的工程.传统水 ...
- 开源低代码框架 ReZero API 正式版本发布 ,界面操作直接生成API
一.ReZero简介 ReZero是一款.NET中间件 : 全网唯一界面操作就能生成API , 可以集成到任何.NET6+ API项目,无破坏性,也可让非.NET用户使用exe文件 免费开源:MIT ...
- vue-element-admin 运行踩坑笔记
npm WARN deprecated svgo@1.3.2: This SVGO version is no longer supported. Upgrade to v2.x.x. npm E ...
- 像阿里OSS一样的文件对像存储服务,容器实现 docker初探及minio测试
像阿里OSS一样的文件对像存储服务,容器实现 docker run -p 8000:9000 --name oss-minio -d -e "MINIO_ACCESS_KEY=AKIAIOS ...
- 宝塔面板6.X在Docker中安装宝塔面板5.9.1 – 我是不是太闲了?
我是不是太闲了,问你们三遍,场景是这样的,我在一台VPS里面安装宝塔面板6.X,宝塔面板的软件商店有Docker管理器2.0这一个免费好用的小玩意.大鸟安装好Docker管理器2.0,然后在Docke ...