Clickhouse一级索引优化方案_clickhouse 索引优化-程序员宅基地

技术标签: 笔记  数据仓库  服务器  数据库  

Clickhouse的应用场景

绝大多数是读请求
数据以相当大的批次(> 1000行)更新,而不是单行更新;或者根本没有更新。
已添加到数据库的数据不能修改。
对于读取,从数据库中提取相当多的行,但只提取列的一小部分。
宽表,即每个表包含着大量的列
查询相对较少(通常每台服务器每秒查询数百次或更少)
对于简单查询,允许延迟大约50毫秒
列中的数据相对较小:数字和短字符串(例如,每个URL 60个字节)
处理单个查询时需要高吞吐量(每台服务器每秒可达数十亿行)
事务不是必须的
对数据一致性要求低
每个查询有一个大表。除了他以外,其他的都很小。
查询结果明显小于源数据。换句话说,数据经过过滤或聚合,因此结果适合于单个服务器的RAM中

1、索引介绍

1.1、主要索引

通过按主键对数据进行物理排序,可以提取其特定值或值范围的数据,延迟低,小于几十毫秒。(也就是下文分析的内容)

  • 改进数据压缩。
    ClickHouse按主键对数据进行排序,因此一致性越高,压缩效果越好。

1.2、二级索引

与其他数据库管理系统不同,ClickHouse 中的二级索引不指向特定的行或行范围。相反,它们允许数据库提前知道某些数据部分中的所有行都与查询筛选条件不匹配,并且根本不读取它们,因此它们被称为数据跳过索引

1.2.1、数据跳过索引

对于系列(**MergeTree)中的表,可以指定数据跳过索引。

这些索引聚合有关块上指定表达式的一些信息,块由粒状组成(粒度的大小是使用表引擎中的设置指定的)。然后,这些聚合用于查询,通过跳过查询无法满足的大块数据块来减少要从磁盘读取的数据量。
ck默认索引粒度:8192 = 1024 * 8
也就是查看表中自动生成的 SETTINGS index_granularity = 8192

2、一级索引优化

总结一下可以优化的地方:

  1. 一级索引
  2. 二级索引
  3. 可以根据现有字段直接分区 PARTITION BY 字段
  4. 可以根据函数分区 PARTITION BY toYYYYMM(EventDate) toYYYYMM是函数,(eventDate)属性需要是Data类型

目前业务中可优化的选项:

  • 日期尽量都使用date类型,目前使用的是Int64(待研究)
    按月分区的键仅允许读取那些包含适当范围的日期的数据块。在这种情况下,数据块可能包含许多日期(最多一整个月)的数据。在块中,数据按主键排序,主键可能不包含日期作为第一列。因此,使用仅具有未指定主键前缀的日期条件的查询将导致读取的数据多于单个日期的数据

  • 针对常查询的字段,使用ORDER BY(c1, c2, xxx) 创建索引,括号中从左到右的优先级会导致查询效率有别;
    注意:

  • 有Nullable的字段,不能创建索引,一些查询字段不要设置Nullable()

2.1、CK的sql的优化方向

  • 分区,原则是尽量把经常一起用到的数据放到相同区(也可以根据where条件来分区),如果一个区太大再放到多个区

大量分区是一种常见的误解。这将导致严重的负面性能影响,包括服务器启动速度慢、插入查询速度慢和选择查询速度慢。表的建议分区总数在1000以下。请注意,分区并不是为了加速SELECT查询(按键排序足以使范围查询变得快速)。分区用于数据操作(删除分区等)

  • 主键(索引,即排序)order by字段选择: 就是把where 里面肯定有的字段加到里面,where 中一定有的字段放到第一位,注意字段的区分度适中即可 区分度太大太小都不好,因为ck的索引时稀疏索引,采用的是按照固定的粒度抽样作为实际的索引值,不是mysql的二叉树,所以不建议使用区分度特别高的字段。

    • 值得一提的是,通常只有在使用SummingMergeTreeAggregatingMergeTree 的时候,才需要同时设置ORDER BYPRIMARY KEY。显式的设置PRIMARY KEY,是为了将主键和排序键设置成不同的值,是进一步优化的体现。(PS:因为我们使用的引擎并不是上面几种,省略PRIMARY KEY。默认情况下,主键与排序键(ORDER BY)相同,所以通常直接使用ORDER BY代为指定主键。)
    • 创建索引尽量选择基数大的,也就是重复相对较多的(因为是稀疏索引) 在mysql中正好是相反的

也就是说,业务中经常需要查询的字段需要放在order by靠前位置。

举个例子:统计了一下业务中的风险实际表的查询sql语句,按照优先级高到低整理出来的字段

create_time , score , vehicle_id,id

也就是说需要这么写:

order  by(create_time , score , vehicle_id,id).

官网原话:长主键会对插入性能和内存消耗产生负面影响 , 但主键中的额外列不会影响查询期间的ClickHouse 性能。

2.2、验证过程

下面只取两个字段create_time,id来验证

创建4个表,插入相同的204w条数据。(开启trace log,查看查询从机器上面读取的条数,不会弄),暂时以执行时间来参考。

img

4个表的排序情况为:

表1: ORDER BY (id)

表2: ORDER BY (id,create_time)

表3: ORDER BY (create_time)

表6: ORDER BY (create_time,id) //推荐

CREATE TABLE hf_ai_hbs.t_base_data_record1
(
    `id` Int64 COMMENT '事件ID',
    `time` Int64 COMMENT '数据记录时间,单位毫秒',
    ...
    省略了一些字段
    ...
    `create_time` Int64 DEFAULT 0 COMMENT '创建时间,毫秒时间戳,设备事件时间'
)
ENGINE = ReplacingMergeTree
ORDER BY id
SETTINGS index_granularity = 8192
CREATE TABLE hf_ai_hbs.t_base_data_record2
(
    `id` Int64 COMMENT '事件ID',
    `time` Int64 COMMENT '数据记录时间,单位毫秒',
    ...
    省略了一些字段
    ...
    `create_time` Int64 DEFAULT 0 COMMENT '创建时间,毫秒时间戳,设备事件时间'
)
ENGINE = ReplacingMergeTree
ORDER BY (id,create_time)
SETTINGS index_granularity = 8192

其余两个字段、属性、数据相同,只是order by里面的内容不同。

-- 执行十次

-- SQL 1
SELECT * FROM t_risk_event_record1 trer WHERE id=171972290612035584; 	--执行十次,单位ms:  平均耗时:16.0
SELECT * FROM t_risk_event_record2 trer WHERE id=171972290612035584;	-- 	平均耗时:19.0
SELECT * FROM t_risk_event_record3 trer WHERE id=171972290612035584;	-- 	平均耗时:23 
SELECT * FROM t_risk_event_record6 trer WHERE id=171972290612035584;	--	平均耗时:25.0
-- SQL 2
SELECT * from t_risk_event_record1 trer order by id DESC LIMIT 3;	--平均耗时:35
SELECT * from t_risk_event_record2 trer order by id DESC LIMIT 3;	--56,50,61,52,57,56,54,55,55,54				平均耗时:55
SELECT * from t_risk_event_record3 trer order by id DESC LIMIT 3;	--平均耗时:138
SELECT * from t_risk_event_record6 trer order by id DESC LIMIT 3;	--平均耗时:148
-- SQL 3
SELECT * from t_risk_event_record1 trer order by create_time DESC LIMIT 3;	--执行十次,平均耗时:125.0	129,122,121,125,133,120,119,125,131,125
SELECT * from t_risk_event_record2 trer order by create_time DESC LIMIT 3;	--执行十次,平均耗时:135.0	146,153,130,140,127,130,125,124,130,145
SELECT * from t_risk_event_record3 trer order by create_time DESC LIMIT 3;	--执行十次,平均耗时:54.0		50,43,56,56,57,54,65,50,61,48
SELECT * from t_risk_event_record6 trer order by create_time DESC LIMIT 3;	--执行十次,平均耗时:53.0		56,57,40,63,53,54,47,44,57,59
-- SQL 4
SELECT * from t_risk_event_record1 trer order by create_time,id DESC LIMIT 3;	--执行十次,平均耗时:120
SELECT * from t_risk_event_record2 trer order by create_time,id DESC LIMIT 3;	--执行十次,平均耗时:135
SELECT * from t_risk_event_record3 trer order by create_time,id DESC LIMIT 3;	--执行十次,平均耗时:100
SELECT * from t_risk_event_record6 trer order by create_time,id DESC LIMIT 3;	--执行十次,平均耗时:65
-- SQL 5
SELECT * from t_risk_event_record1 trer order by id,create_time DESC LIMIT 3;	--执行十次,平均耗时:45
SELECT * from t_risk_event_record2 trer order by id,create_time DESC LIMIT 3;	--执行十次,平均耗时:50
SELECT * from t_risk_event_record3 trer order by id,create_time DESC LIMIT 3;	--执行十次,平均耗时:140
SELECT * from t_risk_event_record6 trer order by id,create_time DESC LIMIT 3;	--执行十次,平均耗时:150
-- SQL 6
SELECT * from t_risk_event_record1 trer WHERE (create_time>=1649324393021) AND (create_time <=1649324595021);	--执行十次,平均耗时:35
SELECT * from t_risk_event_record2 trer WHERE (create_time>=1649324393021) AND (create_time <=1649324595021);	--执行十次,平均耗时:34
SELECT * from t_risk_event_record3 trer WHERE (create_time>=1649324393021) AND (create_time <=1649324595021);	--执行十次,平均耗时:25
SELECT * from t_risk_event_record6 trer WHERE (create_time>=1649324393021) AND (create_time <=1649324595021);	--执行十次,平均耗时:20

img

结论:

  • sql1中,根据id查询,四种情况的查询效率相差不大
  • sql2中,根据id排序,因为建表时候,表1和表2排序键前面都是id,所以查询效率明显比表3、6要好。sql3中情况刚好相反
  • sql4中,表3、4的order by 排序键的create_time比较靠前,所以,他们的查询效率相对表1、2快了两倍多,sql5中相反。
  • sql6中,表3、4的order by 排序键的create_time比较靠前
  • 除了在sql2根据id排序的情况下,表6排序键的查询效率在这几个sql中都是比较快的

2.3、如何修改排序键

img这里有几个问题

  1. 看这段话排序键的AlTER对象是新增的列才行,而不能像二级索引(跳数索引)一样可以随意更改成其他字段

排序键可以更新除了主键外的键。比如主键是id,可以从order by(id,name)更新成(id,age);但是更新的字段必须是新增的列

  1. 更改完排序键后索引文件应该没有任何变化,这时我们设置的新排序规则应该对查询没有任何作用?
  2. 如何才能根据新设置的排序键进行重建索引?
  3. ClickHouse 在什么情况下会自动重建索引?

修改排序键 sql 如下:

ALTER TABLE 数据库.表名
ADD COLUMN age Int16, 
MODIFY ORDER BY (id, age)

总结:

  • 针对常查询的字段,使用ORDER BY(c1, c2, xxx) 创建索引,而且业务中经常需要查询的字段需要放在order by靠前位置。
  • 长主键会对插入性能和内存消耗产生负面影响 , 但主键中的额外列不会影响查询期间的ClickHouse 性能。

参考链接:
clickhouse使用经验总结:https://blog.csdn.net/TankRuning/article/details/111317090

clickhouse的索引结构和查询优化:https://blog.csdn.net/h2604396739/article/details/86172756

从Clickhouse执行计划看跳数/稀疏索引的效果:https://www.gbase8.cn/6596

Clickhouse orderby之谜:https://zhuanlan.zhihu.com/p/379848085

clickhouse 在order by非常慢的情况下优化:https://www.cnblogs.com/niutao/p/15313070.html

ClickHouse的执行计划以及优化策略:https://www.cnblogs.com/traditional/p/15264282.html

clickhouse分区操作实践:https://blog.csdn.net/m0_37813354/article/details/110847747

3、二级索引(跳数索引)

跳数索引:能够快速跳过无用的数据区间(跳跃粒度是我们设置的,默认8192)。有些区间可能不会被扫描到,提高我们查询的效率

类似布隆过滤器,所有跳数索引的原则都是“排除法”,即尽可能的排除那些一定不满足条件的索引粒度。

告诉我们某个数据一定不存在或者可能存在(注意:布隆过滤器是不能判断某条数据一定存在的,存在误报率 (false_positive) )。相比于传统的 List、Set、Map 等数据结构,布隆过滤器更高效、占用空间更少,因为不需要存储原始值,但是缺点是其返回的结果是概率性的,而不是确切的。

知识点

查看Clickhouse的执行计划

在前面加explain

img

注意其中的数据读取位置,分别是从原始的存储(ReadFromStorage),和预处理的源(ReadFromPreparedSource)。

explain SYNTAX 加sql语句,会输出优化的sql,如果和我们写的一样,说明我们的sql已经是最优的了img

查询中的主键和索引

以主键为例。在这种情况下,排序和索引可以如下图示:(CounterID, Date)

img

如果数据查询指定:

  • CounterID in ('a', 'h')中,服务器读取标记和 范围内的数据。[0, 3)[6, 8)
  • CounterID IN ('a', 'h') AND Date = 3中,服务器读取标记和 范围内的数据。[1, 3)``[7, 8)
  • Date = 3中,服务器读取标记范围内的数据。[1, 10] 取并集

上面的示例表明,使用索引始终比完全扫描更有效。

ClickHouse 不需要唯一的主键。可以使用相同的主键插入多行。

选择主键

主键中的列数没有显式限制。根据数据结构,您可以在主键中包含更多或更少的列。这可能:

  • 提高索引的性能。

如果主键为,则在满足以下条件时,添加另一列将提高性能:(a,b)``c

    • 有些查询在列上具有条件, 例如:c
    • 具有相同值的长数据范围(比长几倍)很常见。换句话说,添加另一列时,可以跳过相当长的数据范围。index_granularity (a,b)
  • 改进数据压缩。

ClickHouse按主键对数据进行排序,因此一致性越高,压缩效果越好。

  • 在CollapseingMergeTree和SummingMergeTree 引擎中合并数据部分时提供其他逻辑。在这种情况下,指定与主键不同的排序建是有意义的。

值得一提的是,通常只有在使用SummingMergeTreeAggregatingMergeTree 的时候,才需要同时设置ORDER BYPRIMARY KEY。

显式的设置**PRIMARY KEY,**是为了将主键和排序键设置成不同的值,是进一步优化的体现。

长主键会对插入性能和内存消耗产生负面影响 , 但主键中的额外列不会影响查询期间的ClickHouse 性能。

可以使用语法创建没有主键的表。在这种情况下,ClickHouse将按插入顺序存储数据。如果要在按查询插入数据时保存数据顺序,请设置max_insert_threads = 1ORDER BY tuple() INSERT ... SELECT

若要按初始顺序选择数据,请使用单线程查询。SELECT

分区

单个插入块的分区太多(超过100个)。该限制由“每个插入块的最大分区数”设置控制。大量分区是一种常见的误解。这将导致严重的负面性能影响,包括服务器启动速度慢、插入查询速度慢和选择查询速度慢。表的建议分区总数在1000以下。。请注意,分区并不是为了加速SELECT查询(按键排序足以使范围查询变得快速)。分区用于数据操作(删除分区等)

img

ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate) SETTINGS index_granularity=8192
SELECT count() FROM table WHERE EventDate = toDate(now()) AND CounterID = 34
SELECT count() FROM table WHERE EventDate = toDate(now()) AND (CounterID = 34 OR CounterID = 42)
SELECT count() FROM table WHERE ((EventDate >= toDate('2014-01-01') AND EventDate <= toDate('2014-01-31')) OR EventDate = toDate('2014-05-01')) AND CounterID IN (101500, 731962, 160656) AND (CounterID = 101500 OR EventDate != toDate('2014-05-01'))

ClickHouse 将使用主键索引来修剪不正确的数据,并使用每月分区键来修剪日期范围内的分区。

上面的查询表明,索引甚至用于复杂表达式。对从表中读取数据进行组织,以便使用索引的速度不会比完全扫描慢。

在下面的示例中,无法使用索引。

SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'

要检查 ClickHouse 在运行查询时是否可以使用索引,请使用设置force_index_by_dateforce_primary_key

按月分区的键仅允许读取那些包含适当范围的日期的数据块。在这种情况下,数据块可能包含许多日期(最多一整个月)的数据。在块中,数据按主键排序,主键可能不包含日期作为第一列。因此,使用仅具有未指定主键前缀的日期条件的查询将导致读取的数据多于单个日期的数据。

  • 创建分区

建表时使用关键字 PARTITION BY 【分区名称】

  • 使用函数创建分区

比如根据toYYYYMM(date字段)会生成yyyyMM格式的年月日期分区

数据****PARTITION 在ClickHouse中主要有两方面应用:

  1. 在PARTITION KEY上进行分区裁剪,只查询必要的数据。灵活的PARTITION expression设置,使得可以根据SQL PARTITION 进行分区设置,最大化的贴合业务特点。
  2. 对PARTITION 进行TTL管理,淘汰过期的分区数据。

[

](https://blog.csdn.net/m0_37813354/article/details/110847747)

将索引用于部分单调的主键

例如,考虑一个月中的几天。它们在一个月内形成单调序列,但在更长的时间内不形成单调序列。这是一个部分单调的序列。如果用户使用部分单调的主键创建表,ClickHouse 将像往常一样创建稀疏索引。当用户从此类表中选择数据时,ClickHouse 会分析查询条件。如果用户想要获取索引的两个标记之间的数据,并且这两个标记都在一个月内,ClickHouse 可以在此特定情况下使用索引,因为它可以计算查询参数和索引标记之间的距离。

如果查询参数范围中的主键值不表示单调序列,则 ClickHouse 不能使用索引。在这种情况下,ClickHouse使用完全扫描方法。

ClickHouse 不仅将此逻辑用于月份序列中的天数,还对表示部分单调序列的任何主键使用此逻辑。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_44769557/article/details/124909764

智能推荐

[微服务]API网关(API Gateway)_implementation and performance analysis of api gat-程序员宅基地

文章浏览阅读1.5k次。工作中使用了微服务架构,接下来的一段时间里,我会写一系列的文章来介绍微服务架构,同时我也会在github上写一个microservices的应用框架(地址会在后续文章给出)。这篇文章主要讲述了微服务架构中的API Gateway。 翻译和整理自: http://microservices.io/patterns/apigateway.html 一、问题 微服务架构..._implementation and performance analysis of api gateway for microservices arc

30分钟简易复刻元气骑士地图生成系统_scratch做元气骑士地图-程序员宅基地

文章浏览阅读4.7k次,点赞8次,收藏58次。本文转自Unity Connect博主 超级汽水一个简易的房间类 Roguelike 游戏地图生成系统如果你还不知道《元气骑士》是什么游戏,也许你可以去看看这个视频https://space.bilibili.com/87721937地图显示方式随机大小的矩形房间。随机的房间数量。通过走廊连接每个房间。每个图都有 俩个特殊房间:“出生房”、“传送房”。地图生成思路梳理因为..._scratch做元气骑士地图

南邮23考研复试上机[1015小明与选择题]_南京邮电小明与选择题-程序员宅基地

文章浏览阅读323次。全力以“复”,力争上“邮”~_南京邮电小明与选择题

鼠标聚焦到Text输入框时,按回车键刷新页面原因及解决方法-程序员宅基地

文章浏览阅读87次。前提 一个form中只有一个输入框,当输入框获取焦点后,点击回车,导致整个页面都刷新,问题解决办法。1、处理form在form中添加事件<formonsubmit="return false;">.......</form>2、增加一个隐藏的输入框,认为改变单个输入框<inputid="hiddenText"..._js 焦点在文本框按enter键刷新数据

C#实现MJPEG服务器-程序员宅基地

文章浏览阅读357次。实现流写入,将图像作为mjpeg写入 public class MjpegWriter : IDisposable { private static byte[] CRLF = new byte[] { 13, 10 }; private static byte[] EmptyLine = new byte[] { 13, 10, 13, ..._c# mpeg server

数据源初始化错误:ClassNotFoundException: com.alibaba.druid.pool.DruidDataSourceFactory的解决_caused by java.lang.classnotfoundexception: com.al-程序员宅基地

文章浏览阅读5.4k次,点赞3次,收藏3次。Exceptionsendingcontextinitializedeventtolistenerinstanceofclassorg.springframework.web.context.ContextLoaderListenerorg.springframework.beans.factory.BeanCreationException:Errorcreating..._caused by java.lang.classnotfoundexception: com.alibaba.druid.pool.druiddata

随便推点

npm EPERM: operation not permitted, rename解决-程序员宅基地

文章浏览阅读9.7k次,点赞3次,收藏6次。此问题并非权限问题!执行如下3条命令解决:1.清理npm缓存npm cache clean --force2.升级npm版本npm install -g npm@latest --force3.再次清理npm缓存npm cache clean --force  然后再次运行npm install 即可。转载于:https:/..._npm err! error: eperm: operation not permitted, rename

qt 启动参数 -qws-程序员宅基地

文章浏览阅读262次。运行嵌入式程序在嵌入式QT版本中,程序需要服务器或自己作为服务器程序。服务器程序构造的方法是构造一个QApplication::GuiServe类型的QApplication对象。或者使用-qws命令选项启动程序。Using a Single Display使用-qws选项using Mutiple Displays嵌入式版本中运行多个县市同时运行。两种方式可以实现,要么多次运行服务程序,要么..._qt设置启动参数

【机器学习】朴素贝叶斯算法-程序员宅基地

文章浏览阅读1.4w次,点赞22次,收藏147次。朴素贝叶斯(Naive Bayes)是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法。由于朴素贝叶斯计算联合概率,所以朴素贝叶斯模型属于生成式模型。经典应用案例包括:文本分类、垃圾邮件过滤等。_朴素贝叶斯算法

APT攻击分析-程序员宅基地

文章浏览阅读638次。2019独角兽企业重金招聘Python工程师标准>>> ..._apt - ghostrat - tcp - variant 4

网站使用mysql_使用网站的mysql-程序员宅基地

文章浏览阅读92次。{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云数据库专家保驾护航,为用户的数据库应用系统进行性能和风险评估,参与配合进行数据压测演练,提供数据库优化方面专业建议,在业务高峰期与用户共同保障数据库系统平..._mysql对于网站

YOLOv7训练自己的数据集(超详细)-程序员宅基地

文章浏览阅读6.4w次,点赞74次,收藏808次。YOLOv7训练自己的数据集(超详细)_yolov7训练自己的数据集

推荐文章

热门文章

相关标签