【大数据clickhouse】clickhouse 常用查询优化策略详解

news2024/10/3 0:33:04

一、前言

在上一篇我们分享了clickhouse的常用的语法规则优化策略,这些优化规则更多属于引擎自带的优化策略,开发过程中只需尽量遵守即可,然而,在开发过程中,使用clickhouse更多将面临各种查询sql的编写甚至复杂sql的编写,这就是本篇要探讨的关于clickhouse查询相关的优化策略。

二、关于单表查询相关优化策略

2.1  使用Prewhere 替代 where

Prewhere where 语句的作用相同,用来过滤数据。不同之处在于 prewhere 只支持
*MergeTree 族系列引擎的表,首先会读取指定的列数据,来判断数据过滤,等待数据过滤
之后再读取 select 声明的列字段来补全其余属性
比如,当查询列明显多于筛选列时使用 Prewhere 可十倍提升查询性能, Prewhere 会自动优化
执行过滤阶段的数据读取方式,降低 io 操作。某些场景下, prewhere 语句比 where 语句处理的数据量更少性能更高。
举例来说,我们在使用mysql查询一条数据,如下面的这条sql,在不考虑索引的情况下,正常的执行过程是,先扫描表的数据,然后过滤出name='xxx'的符合条件的记录;
select u.* from user u where u.name = 'xxx'

在clickhouse中,默认情况下,使用where 过程也是类似,但使用了Prewhere 时,并不需要进行全表的扫描了,这样就大大提升了查询的性能;

默认情况下,prewhere自动开启

看下面的查询sql,使用explain查看下

EXPLAIN SYNTAX
select WatchID, 
 JavaEnable, 
 Title, 
 GoodEvent, 
 EventTime, 
 EventDate, 
 CounterID, 
 ClientIP, 
 ClientIP6, 
 RegionID, 
 UserID, 
 CounterClass, 
 OS, 
 UserAgent, 
 URL, 
 Referer, 
 URLDomain, 
 RefererDomain, 
 Refresh, 
 IsRobot, 
 RefererCategories, 
 URLCategories, 
 URLRegions, 
 RefererRegions, 
 ResolutionWidth, 
 ResolutionHeight, 
 ResolutionDepth, 
 FlashMajor, 
 FlashMinor, 
 FlashMinor2
from datasets.hits_v1 where UserID='3198390223272470366';

可以看到引擎自动优化为了prewhere查询

关闭 where 自动转 prewhere

开启下面的配置,可以关闭prewhere功能

set optimize_move_to_prewhere=0; 

再次查询,可以发现prewhere就关闭了;

默认情况,肯定不会关闭 where ,引擎会 自动优化为  prewhere ,但某些场景即使开启优
化,也不会自动转换成 prewhere ,需要手动指定 prewhere,如下:
  • 使用常量表达式;
  • 使用默认值为 alias 类型的字段;
  • 包含了 arrayJOIN,globalIn,globalNotIn 或者 indexHint 的查询;
  • select 查询的列字段和 where 的谓词相同;
  • 使用了主键字段;
比如下面的sql,在这种情况下就不会使用 prewhere ;

2.2  使用数据采样替代全量查询或统计

在某些情况下,比如数据量为TB级别的甚至更大的情况下,不需要精确对数据进行统计而是一个预估值时,可以通过采样运算,这样可极大提升数据分析的性能;
SAMPLE 0.1 # 代表采样 10% 的数据 , 也可以是具体的条数
SELECT Title,count(*) AS PageViews 
FROM hits_v1
SAMPLE 0.1
WHERE CounterID =57
GROUP BY Title
ORDER BY PageViews DESC LIMIT 1000;

可以发现使用这种方式计算统计结果时性能上有明显的提升;

2.3  列裁剪与分区裁剪

列裁剪(减少非必要的查询字段)

表的数据量太大时,应尽量避免使用 select * 操作,一般来说,查询的性能会与查询的字段大小和数量成线性增长,字段越少,消耗的 io 资源越少,性能就会越高;
比如下面这样的sql,就是需要尽量避免的(小编测试在当前服务器下执行界面直接崩掉了);
select * from datasets.hits_v1;

改成下面这样就会好很多;

select WatchID, 
 JavaEnable, 
 Title, 
 GoodEvent, 
 EventTime, 
 EventDate, 
 CounterID, 
 ClientIP, 
 ClientIP6, 
 RegionID, 
 UserID
from datasets.hits_v1;

分区裁剪

就是只读取需要的分区,在过滤条件中指定
select WatchID, 
 JavaEnable, 
 Title, 
 GoodEvent, 
 EventTime, 
 EventDate, 
 CounterID, 
 ClientIP, 
 ClientIP6, 
 RegionID, 
 UserID
from datasets.hits_v1
where EventDate='2014-03-23';

2.4  orderby 结合 where、limit

千万量级以上的数据集进行 order by 查询时,最好搭配 where 条件和 limit 语句一起使用,说到底,就是对查询的结果集数量进行限制,数据量减少了怎么都好说;

SELECT 
    UserID,Age
FROM 
    hits_v1 
WHERE 
    CounterID=57
ORDER BY Age DESC LIMIT 1000;

2.5  避免构建虚拟列

如非必须,不要在结果集上构建虚拟列,虚拟列非常消耗资源浪费性能,可以考虑在前端进行处理,或者在表中构造实际字段进行额外存储;

比如下面的sql,Income/Age 这个就是一个虚拟列,表中并不存在这个字段,而是处于业务的需要构建出来的,不仅是clickhouse,甚至mysql中这种写法也应该是尽量要避免的;

SELECT Income,Age,Income/Age as IncRate FROM datasets.hits_v1;

2.6  使用uniqCombined 替代 distinct

使用uniqCombined 替代 distinct ,性能可提升 10 倍以上,uniqCombined 底层采用类似 HyperLogLog 算法实现,能接收 2%左右的数据误差,可直接使用这种去重方式提升查询性能;

Count(distinct )会使用 uniqExact精确去重,不建议在千万级不同数据上执行 distinct 去重查询,改为近似去重 uniqCombined;

比如查询时可以使用下面这种方式

SELECT uniqCombined(rand()) from datasets.hits_v1;

2.7  其他单表查询中需要注意的事项

(一)查询熔断

为了避免因个别慢查询引起服务雪崩问题,除了可以为单个查询设置超时以外,还可以配置周期熔断,在一个查询周期内,如果用户频繁进行慢查询操作超出规定阈值后将无法继续进行查询操作。

(二)关闭虚拟内存

物理内存和虚拟内存的数据交换,会导致查询变慢,资源允许的情况下建议关闭虚拟内存;

(三)配置 join_use_nulls

为每一个账户添加 join_use_nulls 配置,左表中的一条记录在右表中不存在,右表的相应字段会返回该字段相应数据类型的默认值,而不是标准 SQL 中的 Null 值;

(四)批量写入时先排序

批量写数据时,必须控制每个批次的数据中涉及到的分区的数量,在写入之前最好对需要导入的数据进行排序。因为无序的数据或者涉及的分区太多,会导致 ClickHouse 无法及时对新导入的数据进行合并,从而影响查询性能。

(五)关注 CPU

cpu 一般在 50%左右会出现查询波动,达到 70%会出现大范围的查询超时,cpu 是最关
键的指标,因此运维过程中要非常关注。

三、关于多表关联查询相关优化策略

前置准备

依次执行下面的sql,从源表中复制出两个新表,并从源表中导入部分数据

#创建小表
CREATE TABLE visits_v2 
ENGINE = CollapsingMergeTree(Sign)
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate, intHash32(UserID), VisitID)
SAMPLE BY intHash32(UserID)
SETTINGS index_granularity = 8192
as select * from visits_v1 limit 10000;


#创建 join 结果表:避免控制台疯狂打印数据
CREATE TABLE hits_v2 
ENGINE = MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate, intHash32(UserID))
SAMPLE BY intHash32(UserID)
SETTINGS index_granularity = 8192
as select * from hits_v1 where 1=0;

3.1  使用in代替join

当多表联合查询时,当最终查询的数据仅从其中一张表出来时,可考虑用 IN 操作而不是使用JOIN

这个和clickhouse的自身的关联查询数据加载机制有关,比如A join B时,需要先把B加载到内存中,然后从A中查询的结果跟内存中B的数据进行匹配,如果B表的数据量非常大,很有可能造成内存被耗尽;

正确的使用方式

insert into hits_v2
select a.* from hits_v1 a where a. CounterID in (select CounterID from 
visits_v1);

反例

insert into table hits_v2
select a.* from hits_v1 a left join visits_v1 b on a. CounterID=b. 
CounterID;

3.2  大小表 JOIN

多表join时尽量要满足小表在右的原则,右表关联时被加载到内存中与左表进行比较,ClickHouse 中无论是 Left join 、Right join 还是 Inner join 永远都是拿着右表中的每一条记录到左表中查找该记录是否存在,所以右表必须是小表,这一点必须注意。

下面用两个sql分别模拟下小表在左和小表在右的情况

小表在右

insert into table hits_v2
select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. 
CounterID;

通过执行sql,耗时大概在37秒多

大表在右

insert into table hits_v2
select a.* from visits_v2 b left join hits_v1 a on a. CounterID=b. 
CounterID;

内存超过限制了,直接执行失败

3.3  谓词下推

ClickHouse在join查询时,不会主动发起谓词下推的操作,需要每个子查询提前完成过滤操作,要注意的是,是否执行谓词下推,对性能影响差别很大(新版本中已经不存在此问题,但是需要注意谓词的位置的不同依然有性能的差异);

下面的sql会被优化为 prewhere

Explain syntax
select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. 
CounterID
having a.EventDate = '2014-03-17';


Explain syntax
select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. 
CounterID
having b.StartDate = '2014-03-17'

 

3.4  分布式表使用GLOBAL

在真实的线上环境,clickhouse通常多节点部署,在这种情况下,如果两张分布式表上在使用 IN 和 JOIN 之前,必须加上 GLOBAL 关键字,右表只会在接收查询请求的那个节点查询一次,并将其分发到其他节点上。如果不加 GLOBAL 关键字的话,每个节点都会单独发起一次对右表的查询,而右表又是分布式表,就导致右表一共会被查询 N²次(N是该分布式表的分片数量),这就是查询放大,会带来很大开销;

3.5  使用字典表

将一些需要关联分析的业务,创建成字典表进行 join 操作,前提是字典表不宜太大,因为字典表会常驻内存;

3.6  提前过滤数据

通过增加逻辑过滤以减少数据行的扫描,达到提高执行速度及降低内存消耗的目的,这个和mysql的思想是一致的;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/347837.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP(13)HTTP协议

PHP(13)HTTP协议一、HTTP请求1. 请求行2. 请求头3. 请求体二、HTTP响应1. 响应行2. 响应头三、设置HTTP响应四、模拟HTTP请求一、HTTP请求 1. 请求行 请求行独占一行。形式:请求方式 资源路径 协议版本号 GET /index.php HTTP/1.1 2. 请求…

vue3:直接修改reative的值,页面却不响应,这是什么情况?

目录 前言 错误示范: 解决办法: 1.使用ref 2.reative多套一层 3.使用Object.assign 前言: 今天看到有人在提问,问题是这样的,我修改了reative的值,数据居然失去了响应性,页面毫无变化&…

Linux多进程开发

一、进程概述 1、程序和进程 程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程: 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。内核利用此信息来解释文件中的其他信息。(ELF可执行连…

vue项目第五天

历史记录组件触发 连带menu组件路由同步修改菜单 默认激活的路由。历史记录路由如果当前路由在最后一个位置,删除则自动向前移位。解决第一个小bug,点击tab高亮之后路由不跳点击最后一个tab删除,修改了menuid,同步路由跳转点击删除…

2023年企业如何改善员工体验?为什么员工体验很重要?

什么是员工体验?大约 96% 的企业领导者表示,专注于员工体验可以更轻松地留住顶尖人才。[1] 这还不是全部。令人震惊的是,87%的企业领导者还表示,优先考虑员工的幸福感将给他们带来竞争优势。尽管有这些发现,但只有19%的…

redis五大IO网络模型、内存回收

目录1.0用户空间和内核态空间1.1 网络模型-阻塞IO1.2 网络模型-非阻塞IO1.3 网络模型-IO多路复用1.3.1 网络模型-IO多路复用-select方式1.3.2 网络模型-IO多路复用模型-poll模式1.3.3 网络模型-IO多路复用模型-epoll函数1.3.4 网络模型-epoll中的ET和LT1.3.5 网络模型-基于epol…

Hadoop高可用搭建(二)

目录 解压Hadoop 改名 更改配置文件 workers hdfs-site.xml core-site.xml hadoop-env.sh mapred-site.xml yarn-site.xml 设置环境变量 启动集群 启动zk集群 启动journalnode服务 格式化hfds namenode 启动namenode 同步namenode信息 查看namenode节点状态 …

三维重建小有基础入门之特征点检测基础

前言:本文将从此篇开始,记录自己从普通CVer入门三维重建的学习过程,可能过程比较坎坷,都在摸索阶段,但争取每次学习都能进一步,提高自己的能力,同时,每篇文章都会按情况相应地推出B站…

一文搞懂core-scheduling核心机制

cookie的原理借助于unsigned long型,和refcount_t引用计数器。 32位64位char *4字节8字节unsigned long4字节8字节 数据结构修改 首先看看实现core scheduling功能对数据结构有哪些修改 task_struct struct task_struct{struct rb_node core_node;unsigned long…

Downie4.6.7

Downie是Mac下一个简单的下载管理器,可以让您快速将不同的视频网站上的视频下载并保存到电脑磁盘里然后使用您的默认媒体播放器观看它们,文章末尾附下载地址。主要特点支持许多网站目前支持超过1,000个不同的网站(包括YouTube,Vim…

06 OpenCV‘阈值处理、自适应处理与ostu方法

1 基本概念 CV2中使用阈值的作用是将灰度图像二值化,即将灰度图像的像素值根据一个设定的阈值分成黑白两部分。阈值处理可以用于图像分割、去除噪声、增强图像对比度等多个领域。例如,在物体检测和跟踪中,可以通过对图像进行阈值处理来提取目…

回收站清空了还能找回来吗?回收站恢复的4个方法(最全)

回收站作为一个数据回收的地方,可以保存已删除的文件很久,直到用户手动永久删除这些数据,这为用户避免了许多数据丢失的问题。但是回收站数据过多,难免会影响电脑的运行速度。为此,我们都会定期进行清理。 清理过程中…

LeetCode 92. 反转链表 II

原题链接 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 给你单链表的头指针 headheadhead 和两个整数 leftleftleft 和 rightrightright &#xff0c;其中 left<rightleft < rightleft<right 。请你反转从位置 leftleftleft 到位置 rightrightrig…

Scala 入门(第一章Scala 环境搭建、插件的安装)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 第 1 章 Scala 入门1.1 概述1.1.1 为什么学习 Scala1.1.2 Scala 发展历史1.1.3 Scala 和 Java 关系1.1.4 Scala 语言特点1.2 Scala 环境搭建1.3 Scala 插件安装1.4 HelloWorl…

Web 容器、HTTP 服务器 、Servlet 容器区别与联系

首先浏览器发起 HTTP 请求&#xff0c;像早期的时候只会请求一些静态资源&#xff0c;这时候需要一个服务器来处理 HTTP 请求&#xff0c;并且将相应的静态资源返回。 这个服务器叫 HTTP 服务器。 简单点说就是解析请求&#xff0c;然后得知需要服务器上面哪个文件夹下哪个名字…

三【mybatis的批量删除】

目录一 .批量删除的流程1.1 快速创建实体类1.1.1 创建完毕后 除了属性 其他内容进行删除1.2 创建mapper接口的抽象方法1.3 编写sql语句1.4 测试类Arrays.asList(删除对应的 id 值)运行结果✅作者简介&#xff1a;Java-小白后端开发者 &#x1f96d;公认外号&#xff1a;球场上的…

C语言指针剖析(初阶) 最详细!

什么是指针&#xff1f;指针和指针类型野指针指针运算指针和数组二级指针指针数组什么是指针&#xff1f;指针是内存中一个最小单元的编号&#xff0c;也就是地址。1.把内存划分为一个个的内存单元&#xff0c;一个内存单元的大小是一个字节。2.每个字节都给定唯一的编号&#…

GEE学习笔记九十一:栅格影像叠置分析

最近发现好多人都在问一个问题&#xff0c;两张影像如何取其相交区域&#xff1f;其实这个问题简单来讲就是多张栅格影像进行叠加分析。在GEE中栅格影像不像矢量数据那样有直接的函数来做数据分析&#xff0c;需要我们自己手动写一些代码来实现这些操作。要实现这个功能有很多方…

微信怎么开小店?【企业商家微信开店】

企业商家入局微信做营销已经是经营规划中必须做的一件事了&#xff0c;对于企业商家来说&#xff0c;最简单直接的方式就是开一个微信小店&#xff0c;然后通过自己宣传推广来在微信小店中成商品。那么企业商家在微信怎么开小店呢&#xff1f;下面内容分享给想在微信开店的企业…

滑块连杆模型仿真(含三菱ST+博途SCL计算源代码)

由滑块位置逆计算曲柄角度,请参看下面的文章链接,这篇博客主要计算由曲柄转动角度计算滑块位置。 https://blog.csdn.net/m0_46143730/article/details/128943228https://blog.csdn.net/m0_46143730/article/details/128943228简单的看下连杆滑块模型 模型的数学推导过程,大…