当前位置: 首页 > news >正文

实战攻略 | ClickHouse优化之FINAL查询加速

【本文作者:擎创科技资深研发 禹鼎侯】

查询时为什么要加FINAL

我们在使用ClickHouse存储数据时,通常会有一些去重的需求,这时候我们可以使用ReplacingMergeTree引擎。这个引擎允许你存储重复数据,但是在merge的时候会根据order by的字段进行去重。

它的去重逻辑是使用新数据覆盖旧数据。

但是很多时候,merge并不是实时的,他可能会在数据插入后几分钟甚至十几分钟后才会进行,而我们对数据的查询却往往却是实时的。这个时候就必然出现一个问题:

那些还没有来得及merge的数据,查询出来会有重复。这与我们所期望的效果是背道而驰的。

事实上,即便是merge发生了,我们也不能保证数据一定没有重复。我们举个简单的例子:

如上图所示:p1~p4是四个原始part,发生合并之后数据进行了去重,最终合并成了p1_4, 对id去重之后有四个值,分别为1,2,3,4, 假设此时又有一个新part p5插入,此时去查询仍然能搜到两条id为2和3数据。

因此,即使发生了合并,我们也不能保证数据就一定是唯一的。

ClickHouse为了解决这个问题,提供了FINAL语法,从字面意义上理解,就是返回merge最终态的数据结果。它的效果与OPTIMIZE FINAL 是一致的。

不过,SELECT FINAL仅是在读时合并,并不会实际将底层数据合并,而OPTIMIZE FINAL则是实实在在的发生合并,这是二者的本质的区别。

FINAL查询会有什么问题

那么大家肯定也已经发现了。这种加了FINAL的查询,性能会有很大的问题!

因为FINAL相当于在查询时执行一次OPTIMIZE FINAL,而这个操作本质上是将同一个partition内的数据合并成一个part。在数据量不大的情况下还好,这个操作还能很快返回,如果数据集比较大,比如单个分区的数据已经达到了上千万级,甚至上亿级,那么一次FINAL可真是要了亲命了。

为了让大家直观的感受到这种性能差距,我们来举个例子。

我们使用ClickHouse官方提供的压测数据来进行举例:

https://clickhouse.com/docs/en/getting-started/example-datasets/opensky#validate-data

建表语句如下:

CREATE TABLE opensky(    `callsign` String,    `number` String,    `icao24` String,    `registration` String,    `typecode` String,    `origin` String,    `destination` String,    `firstseen` DateTime,    `lastseen` DateTime,    `day` DateTime,    `latitude_1` Float64,    `longitude_1` Float64,    `altitude_1` Float64,    `latitude_2` Float64,    `longitude_2` Float64,    `altitude_2` Float64)ENGINE = ReplacingMergeTreePARTITION BY toYYYYMMDD(day)ORDER BY (origin, destination, callsign)

我们将数据导入两遍,这样就一定会得到重复的数据:

ck94 :) select count() from opensky;
SELECT count()FROM opensky
Query id: 0d65affc-873e-4847-9ee0-ae749b091bd1
┌───count()─┐│ 127132244 │└───────────┘
1 row in set. Elapsed: 0.007 sec.

接下来我们来执行一个查询语句:

ck94 :) select avg(altitude_1) from opensky where day >= '2020-10-01 00:00:00' and typecode = 'B737';
SELECT avg(altitude_1)FROM openskyWHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737')
Query id: bcb16bd6-91fc-4993-a4de-87e1731d7760
┌────avg(altitude_1)─┐│ 1458.7475907858743 │└────────────────────┘
1 row in set. Elapsed: 0.186 sec. Processed 34.74 million rows, 812.04 MB (186.88 million rows/s., 4.37 GB/s.)Peak memory usage: 14.39 MiB.

可以看到,当我们不使用final时,上面这个sql仅仅使用了0.186秒就返回了结果。

接下来我们看看加了final的效果:

ck94 :) select avg(altitude_1) from opensky final where day >= '2020-10-01 00:00:00' and typecode = 'B737';
SELECT avg(altitude_1)FROM openskyFINALWHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737')
Query id: 63f760a6-9bca-4a40-a7b3-237eacae356a
┌────avg(altitude_1)─┐│ 1336.4280164372838 │└────────────────────┘
1 row in set. Elapsed: 12.784 sec. Processed 48.60 million rows, 3.06 GB (3.80 million rows/s., 239.37 MB/s.)Peak memory usage: 4.16 GiB.

上面这个SQL足足耗费了12.784秒!这可是近70倍的差距,我们当前的数据集还不算大,如果数据集再大一点,那么这个查询性能慢的问题将无限扩大化,最终影响到生产使用。

这让老夫如何是好

那么,这个问题如此明显,有没有什么好的优化手段呢?

自然是有的。

接下来就来介绍两种方法来做final查询的优化。

优化手段1:使用PREWHERE

所谓PREWHERE,就是在查询之前,先将数据过滤掉一部分,这样,目标数据集的规模小了,执行FINAL自然会快了。

我们使用EXPLAIN  SYNTAX 执行计划来看一下为啥第一条不加FINAL的SQL快得离谱:

EXPLAIN SYNTAXSELECT avg(altitude_1)FROM openskyWHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737')
Query id: 046a721e-7690-4d0b-9457-20e825d0e152
┌─explain─────────────────────────────────────────────────────────┐│ SELECT avg(altitude_1)                                          ││ FROM opensky                                                    ││ PREWHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737') │└─────────────────────────────────────────────────────────────────┘
3 rows in set. Elapsed: 0.003 sec. 

可以看到,上面这条SQL,实际上clickhouse优化器已经对其进行改写成了PREWHERE。

我们再看看加了FINAL的执行计划:

EXPLAIN SYNTAXSELECT avg(altitude_1)FROM openskyFINALWHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737')
Query id: c4ae0a63-7a55-4216-815a-25ff44ee538e
┌─explain──────────────────────────────────────────────────────┐│ SELECT avg(altitude_1)                                       ││ FROM opensky                                                 ││ FINAL                                                        ││ WHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737') │└──────────────────────────────────────────────────────────────┘

那就离了个大谱了!

为神马就加了个FINAL,他就不能使用PREWHERE优化了?

按照官方的说法: 如果查询条件里带有主键字段,clickhouse会默认会使用PREWHERE进行优化,可以提前减少数据集的大小,一来避免过多内存造成OOM,二来自然是可以加速查询了。

然而十分悲催的是,这个优化对加了FINAL的查询不会生效。

那么我们怎么能让它使用到PREWHERE优化呢?

这里提供两种方法:

一种是显式指定。

ck94 :) select avg(altitude_1) from opensky final prewhere day >= '2020-10-01 00:00:00' and typecode = 'B737';
SELECT avg(altitude_1)FROM openskyFINALPREWHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737')
Query id: ce72c7a9-38a1-4ae8-957e-d951e0dd9d0b
┌────avg(altitude_1)─┐│ 1424.1051732278174 │└────────────────────┘
1 row in set. Elapsed: 1.429 sec. Processed 48.60 million rows, 3.06 GB (34.01 million rows/s., 2.14 GB/s.)Peak memory usage: 1.19 GiB.

可以看到,我们改成显式使用PREWHERE之后,查询性能立马减少到了1.429秒,差不多有9倍左右的提升,可见这个提升是巨大的。

第二是利用子查询命中PREWHERE。

那么,有木有什么办法不显式指定PREWHERE,但是让其有这个优化呢?而且我们并不能保证所有的条件都会带上主键索引。

前面介绍过,当查询条件带主键,且不加final的时候,clickhouse会默认使用PREWHERE进行优化,那么我们是不是可以先用子查询命中PREWHERE,然后再final呢?

我们将SQL改成如下样子:

ck94 :) select avg(altitude_1) from opensky where (origin, destination, callsign) in (select origin, destination, callsign from opensky where day >= '2020-10-01 00:00:00' and typecode = 'B737') and day >= '2020-10-01 00:00:00' and typecode = 'B737';
SELECT avg(altitude_1)FROM openskyWHERE ((origin, destination, callsign) IN (    SELECT        origin,        destination,        callsign    FROM opensky    WHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737'))) AND (day >= '2020-10-01 00:00:00') AND (typecode = 'B737')
Query id: 61ae0590-de1a-44b1-866f-0c54a919c762
┌────avg(altitude_1)─┐│ 1458.7475907858748 │└────────────────────┘
1 row in set. Elapsed: 0.768 sec. Processed 69.47 million rows, 4.09 GB (90.43 million rows/s., 5.32 GB/s.)Peak memory usage: 69.15 MiB.

耗时降到了0.768秒,这已经非常接近不加final的裸查询了。牛逼class!

优化手段2:禁用final查询跨分区merge

ClickHouse本身是允许跨分区进行数据替换的,这无疑复杂化了FINAL查询的逻辑。但事实上,只要我们的数据分区合理,这种情况完全可以规避掉。这时候我们可以使用do_not_merge_across_partitions_select_final=1这个配置来禁用跨final查询时分区合并。这意味着clickhouse仅在本分区内进行merge去重。

我们来看看效果:

ck94 :) select avg(altitude_1) from opensky final where day >= '2020-10-01 00:00:00' and typecode = 'B737' SETTINGS do_not_merge_across_partitions_select_final=1;
SELECT avg(altitude_1)FROM openskyFINALWHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737')SETTINGS do_not_merge_across_partitions_select_final = 1
Query id: 41aa4fc9-5933-43e1-86bf-a374eab15dd9
┌────avg(altitude_1)─┐│ 1458.6043678101414 │└────────────────────┘
1 row in set. Elapsed: 0.732 sec. Processed 34.74 million rows, 2.18 GB (47.44 million rows/s., 2.98 GB/s.)Peak memory usage: 2.86 GiB.
仅耗时0.732秒,同样牛的一批。

事实上,我们建议在部署集群时,将这个配置作为默认配置进行设置,在知名项目clickhouse-operator 中,这个配置就是默认打开的。

那么,综合上面两种优化手段,这就是最终形态了:

ck94 :) select avg(altitude_1) from opensky where (origin, destination, callsign) in (select origin, destination, callsign from opensky where day >= '2020-10-01 00:00:00' and typecode = 'B737') and day >= '2020-10-01 00:00:00' and typecode = 'B737' SETTINGS do_not_merge_across_partitions_select_final = 1;
SELECT avg(altitude_1)FROM openskyWHERE ((origin, destination, callsign) IN (    SELECT        origin,        destination,        callsign    FROM opensky    WHERE (day >= '2020-10-01 00:00:00') AND (typecode = 'B737'))) AND (day >= '2020-10-01 00:00:00') AND (typecode = 'B737')SETTINGS do_not_merge_across_partitions_select_final = 1
Query id: 298c3d92-67da-4379-8e1d-43f85c99cc2c
┌────avg(altitude_1)─┐│ 1458.7475907858745 │└────────────────────┘
1 row in set. Elapsed: 0.678 sec. Processed 69.47 million rows, 4.09 GB (102.45 million rows/s., 6.03 GB/s.)Peak memory usage: 69.89 MiB.

虽然还是比不加final的要慢一丢丢,但总体上已经到了可以接受的程度了。

———— THE END ————

相关文章:

实战攻略 | ClickHouse优化之FINAL查询加速

【本文作者:擎创科技资深研发 禹鼎侯】 查询时为什么要加FINAL 我们在使用ClickHouse存储数据时,通常会有一些去重的需求,这时候我们可以使用ReplacingMergeTree引擎。这个引擎允许你存储重复数据,但是在merge的时候会根据order …...

5G NR gNB 逻辑架构及其功能拆分选项

5G NR gNB 逻辑架构及其功能拆分选项 中央单元 (CU) 和分布式单元功能拆分选项RAN 分体架构的优势在哪里使用哪个拆分函数?参考: 5G NR gNB Logical Architecture and It’s Functional Split OptionsCentral Unit (CU) and Distributed Unit Functional…...

PyQt入门指南四十六 性能优化策略

在PyQt应用程序中,性能优化是一个重要的考虑因素,尤其是在处理大型数据集或复杂图形界面时。以下是一些常见的性能优化策略: 1. 使用延迟加载(Lazy Loading) 延迟加载是一种优化技术,只在需要时加载资源。…...

【RMA】基于知识注入和模糊学习的多模态歧义分析

abstract 多模态情感分析(MSA)利用互补的多模态特征来预测情感极性,主要涉及语言、视觉和音频三种模态。现有的多模态融合方法主要考虑不同模态的互补性,而忽略了模态之间的冲突所导致的歧义(即文本模态预测积极情绪&…...

CulturalBench :一个旨在评估大型语言模型在全球不同文化背景下知识掌握情况的基准测试数据集

2024-10-04,为了提升大型语言模型在不同文化背景下的实用性,华盛顿大学、艾伦人工智能研究所等机构联合创建了CulturalBench。这个数据集包含1,227个由人类编写和验证的问题,覆盖了包括被边缘化地区在内的45个全球区域。CulturalBench的推出&…...

Git 入门篇(一)

前言 操作系统:win11 64位 与gitee搭配使用 Git 入门篇(一) Git 入门篇(二) Git 入门篇(三) 目录 git下载、安装与配置 下载 安装 配置 git下载、安装与配置 下载 官网:git-…...

一个灵活且功能强大的动画库 Popmotion

一个灵活且功能强大的动画库 Popmotion 什么是 Popmotion? Popmotion 是一个强大的 JavaScript 动画库,提供了一系列简洁的 API,方便开发者创建流畅的动画效果。它支持不同类型的动画,包括 CSS 动画、SVG 动画和 DOM 动画&#…...

如何解决传统能源企业后备人才不足、人才规划缺失问题

如何解决传统能源企业后备人才不足、人才规划缺失问题 很多传统能源企业都面临着老员工逐渐退休,新员工还没有培养起来的问题,缺乏提前对人力资源规划的意识,导致当企业要开展新业务时或者老员工离职的时候,缺乏合适的人选。特别…...

PDF模板制作与填充(Java)

1.PDF模板制作 准备原始模板 准备一个原始PDF模板,可以编辑好Word,预留出要填充的部分,再转换成PDF格式。 设置表单域 用任意PDF编辑器打开PDF模板文件,设置表单域,下面以WPS为例: 拖动文本域到需要填充的…...

LeetCode题练习与总结:迷你语法分析器--385

一、题目描述 给定一个字符串 s 表示一个整数嵌套列表,实现一个解析它的语法分析器并返回解析的结果 NestedInteger 。 列表中的每个元素只可能是整数或整数嵌套列表 示例 1: 输入:s "324", 输出:324 解释&#xff…...

Unity WebGL交互通信

Unity 调用 H5 本文使用的 unity 版本为:2021.3.3 1.在unity中通过c#的特性DllImport导出外部实现函数 [DllImport("__Internal")]private static extern void callJsString(string param);[DllImport("__Internal")]private static extern vo…...

王道考研之数据结构

数据结构系列 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 数据结构 数据结构系列1.线性表1.1 线性表的定义和相关概念1.2 线性表的创销 增删查改 判空表长打印 2.顺序表2.1 顺序表定义和相关概念2.2 顺序表的静态实现2.3 顺序表的…...

实习冲刺Day17

算法题 x的平方根 69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int mySqrt(int x) {long left 0,right x;//定义左右边界//数值取的大longlong类型while (left < right) {long mid (right-left1)/2left;//定义中间节点if ((mid *…...

我自己nodejs练手时常用的一些库基础用法

我自己在使用nodejs以及前端实战练习时常用的一些库的基本使用 1.bcrypt //注册账号时&#xff0c;给密码加密 password是前端传过来的密码&#xff0c;hashPassword是存到数据库中的密码 const bcrypt require(bcrypt) const hashPassword bcrypt.hash(password,10) //登…...

岛屿数量问题

给一个0 1矩阵&#xff0c;1代表是陆地&#xff0c;0代表海洋&#xff0c; 如果两个1相邻&#xff0c;那么这两个1属于同一个岛。我们只考虑上下左右为相邻。 岛屿问题: 相邻陆地可以组成一个岛屿&#xff08;相邻:上下左右&#xff09; 判断岛屿个数。 C 解决方案 #include &…...

智能制造基础- TPM(全面生产维护)

TPM 前言一、TPM二、TPM实施步骤三、 消除主要问题3.1 实施指南3.2 如何进行“主要问题”的消除&#xff1f; 四、自主维护4.1 实施指南4.2 主要工作内容4.3 如何进行“自主维护“ 五、计划维护5.1 实施指南5.2 如何实施计划维护 六、TPM 适当的 设备 设计5.1 实施指南5.2 如何…...

C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(4)

2.2.2、显式实例化 有危险存在于有些类模板成员函数的编译错误&#xff0c;在隐式实例化时没有注意到。未被使用的类模板成员函数也可能包含语法错误&#xff0c;因为它们不会被编译到。这会使得检测代码的语法错误很困难。可以强制编译器生成所有成员函数的代码&#xff0c;vi…...

【力扣热题100】[Java版] 刷题笔记-160. 相交链表

题目&#xff1a;160. 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意…...

多线程和线程同步复习

多线程和线程同步复习 进程线程区别创建线程线程退出线程回收全局写法传参写法 线程分离线程同步同步方式 互斥锁互斥锁进行线程同步 死锁读写锁api细说读写锁进行线程同步 条件变量生产者消费者案例问题解答加强版生产者消费者 总结信号量信号量实现生产者消费者同步-->一个…...

贝式计算的 AI4S 观察:使用机器学习对世界进行感知与推演,最大魅力在于横向扩展的有效性

「传统研究方法高度依赖于科研人员自身的特征和问题定义能力&#xff0c;通常采用小数据&#xff0c;在泛化能力和拓展能力上存疑。而 AI 研究方法则需要引入大规模、高质量数据&#xff0c;并采用机器学习进行特征抽取&#xff0c;这使得产生的科研结果在真实世界的问题中非常…...

容器化技术入门:Docker详解

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 容器化技术入门&#xff1a;Docker详解 容器化技术入门&#xff1a;Docker详解 容器化技术入门&#xff1a;Docker详解 引言 Doc…...

基于SSM(Spring + Spring MVC + MyBatis)框架的药房管理系统

基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架的药房管理系统 项目概述 功能需求 用户管理&#xff1a;管理员可以添加、删除、修改和查询用户信息。药品管理&#xff1a;支持对药品信息的增删改查操作&#xff0c;包括药品名称、价格、库存量等。供应商…...

在服务器里安装2个conda

1、安装新的conda 下载地址&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 本文选择&#xff1a;Anaconda3-2023.03-1-Linux-x86_64.sh 安装&#xff1a;Ubuntu安装Anaconda详细步骤&#xff08;Ubuntu22.04.1&#xff…...

web安全漏洞之ssrf入门

web安全漏洞之ssrf入门 1.什么是ssrf SSRF(Server Side Request Forgery,服务端请求伪造)是一种通过构造数据进而伪造成服务端发起请求的漏洞。因为请求是由服务器内部发起&#xff0c;所以一般情况下SSRF漏洞的目标往往是无法从外网访问的内系统。 SSRF漏洞形成的原理多是服务…...

《NoSQL 基础知识总结》

在当今的数据存储和管理领域&#xff0c;NoSQL 数据库正逐渐崭露头角&#xff0c;成为许多应用场景下的有力选择。今天&#xff0c;我们就来一起深入了解一下 NoSQL 的基础知识吧。 一、什么是 NoSQL&#xff1f; NoSQL&#xff0c;即 “Not Only SQL”&#xff0c;它是一种不…...

高校宿舍信息管理系统小程序

作者主页&#xff1a;编程千纸鹤 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参…...

2.索引:MySQL 索引分类

MySQL中的索引是提高数据查询速度的重要工具&#xff0c;就像一本书的目录&#xff0c;可以帮助我们快速定位到所需的内容。选择适合的索引类型对数据库设计和性能优化至关重要。本文将详细介绍MySQL中常见的索引类型&#xff0c;并重点讲解聚集索引和二级索引的概念及应用。 1…...

sklearn红酒数据集分类器的构建和评估

实验目的&#xff1a; 1. 掌握sklearn科学数据包中决策树和神经网络分类器的构建 2. 掌握对不同分类器进行综合评估 实验数据&#xff1a; 红酒数据集 红酒数据集利用红酒的化学特征来描述三种不同类型的葡萄酒。 实验内容与要求&#xff1a; 解压文件得到wine数据。利用pa…...

【IC验证面试常问-4】

IC验证面试常问-4 1.11 struct和union的异同1.13 rose 和posedge 的区别&#xff1f;1.14 semaphore的用处是什么&#xff1f;1.15 类中的静态方法使用注意事项有哪些&#xff1f;1.16 initial和final的区别&#xff1f; s t o p , stop, stop,finish的区别1.17 logic,wire和re…...

【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!

数据集介绍 【数据集】道路事故识别数据集 8939 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。数据集中包含2种分类&#xff1a;{0: accident, 1: non-accident}。数据集来自国内外图片网站和视频截图。检测范围道路事故检测、监控视角检测、无人机视角检测、等&…...