ClickHouse SQL 查询优化

本文介绍了ClickHouse SQL查询的优化方法,包括单表查询和多表关联的优化策略。单表查询中,推荐使用Prewhere替代where,数据采样,列裁剪与分区裁剪等;多表关联时,建议用IN代替JOIN,注意谓词下推和使用GLOBAL关键字。优化还包括使用物化视图,避免构建虚拟列,以及使用uniqCombined替代distinct等。
摘要由CSDN通过智能技术生成

单表查询

1.1 Prewhere替代where

Prewherewhere语句的作用相同用来过滤数据。不同之处在于prewhere只支持 *MergeTree 族系列引擎的表,首先会读取指定的列数据,来判断数据过滤,等待数据过滤之后再读取select 声明的列字段来补全其余属性。

当查询列明显多于筛选列时使用Prewhere可十倍提升查询性能,Prewhere会自动优化执行过滤阶段的数据读取方式,降低io操作。

在某些场合下,prewhere语句比where语句处理的数据量更少性能更高。

#关闭where自动转prewhere(默认情况下, where条件会自动优化成prewhere)
set optimize_move_to_prewhere=0; 
# 使用where
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关键字
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 prewhere UserID='3198390223272470366';

默认情况,我们肯定不会关闭where自动优化成prewhere,但是某些场景即使开启优化,也不会自动转换成prewhere,需要手动指定prewhere:

  • 使用常量表达式
  • 使用默认值为alias类型的字段
  • 包含了arrayJOIN,globalIn,globalNotIn或者indexHint的查询
  • select查询的列字段和where的谓词相同
  • 使用了主键字段

1.2 数据采样

通过采样运算可极大提升数据分析的性能

SELECT Title,count(*) AS PageViews 
FROM hits_v1
SAMPLE 0.1         #代表采样10%的数据,也可以是具体的条数
WHERE CounterID =57
GROUP BY Title
ORDER BY PageViews DESC LIMIT 1000

采样修饰符只有在MergeTree engine表中才有效,且在创建表时需要指定采样策略。

1.3 列裁剪与分区裁剪

数据量太大时应避免使用select * 操作,查询的性能会与查询的字段大小和数量成线性表换,字段越少,消耗的io资源越少,性能就会越高。

反例:
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';

1.4 orderby 结合 where、limit

千万以上数据集进行order by查询时需要搭配where条件和limit语句一起使用。

#正例:
SELECT UserID,Age
FROM hits_v1        
WHERE CounterID=57
ORDER BY Age DESC LIMIT 1000

#反例:
SELECT UserID,Age
FROM hits_v1        
ORDER BY Age DESC

1.5 避免构建虚拟列

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

反例:
SELECT Income,Age,Income/Age as IncRate FROM datasets.hits_v1;
正例:拿到Income和Age后,考虑在前端进行处理,或者在表中构造实际字段进行额外存储
SELECT Income,Age FROM datasets.hits_v1;

1.6 uniqCombined替代distinct

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

不建议在千万级不同数据上执行distinct去重查询,改为近似去重uniqCombined

反例:
select count(distinct rand()) from hits_v1;
正例:
SELECT uniqCombined(rand()) from  datasets.hits_v1

1.7 使用物化视图

参考第6章。

1.8 其他注意事项

(1)查询熔断

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

(2)关闭虚拟内存

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

(3)配置join_use_nulls

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

(4)批量写入时先排序

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

(5)关注CPU

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

多表关联

2.1 准备表和数据

#创建小表
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;

2.2 用 IN 代替 JOIN

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

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

#反例:使用join
insert into table hits_v2
select a.* from hits_v1 a left join visits_v1 b on a. CounterID=b. CounterID;

2.3 大小表JOIN

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

(1)小表在右

insert into table hits_v2

select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. CounterID;

2大表在右

insert into table hits_v2

select a.* from visits_v2 b left join hits_v1 a on a. CounterID=b. CounterID;

2.4 注意谓词下推(版本差异)

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

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';

insert into hits_v2
select a.* from hits_v1 a left join visits_v2 b on a. CounterID=b. CounterID
where a.EventDate = '2014-03-17';

insert into hits_v2
select a.* from (
    select * from 
    hits_v1 
    where EventDate = '2014-03-17'
) a left join visits_v2 b on a. CounterID=b. CounterID;

2.5 分布式表使用GLOBAL

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

2.6 使用字典表

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

2.7 提前过滤

通过增加逻辑过滤可以减少数据扫描,达到提高执行速度及降低内存消耗的目的

ClickHouse 是一种高性能的列式数据库管理系统 (DBMS),主要用于实时数据分析、日志处理和大数据分析等场景。它特别适合于需要快速响应查询并能够处理大量数据的应用。 ### ClickHouse SQL 解析工具 #### 作用 点击屋 SQL 解析工具主要用于帮助用户理解和调试 ClickHouse 查询语句。它提供了一个可视化的界面,允许用户输入 SQL 查询,并查看其内部表示形式(如 AST 树)、优化步骤以及最终生成的执行计划。这对于理解如何优化查询性能、调试错误和学习 ClickHouse查询语法非常有帮助。 #### 功能特性 1. **SQL 语法检查**:验证输入的 SQL 语句是否符合 ClickHouse 的语法规范,帮助识别潜在的语法错误。 2. **查询树可视化**:展示查询的抽象语法树(AST),有助于了解查询执行的逻辑流程。 3. **优化步骤显示**:展示 SQL 查询优化过程,包括对表的访问模式选择、索引使用情况等方面的优化决策。 4. **执行计划查看**:呈现最终用于执行查询的详细计划,包括数据读取策略、内存分配、并行度设置等信息。 #### 使用方法 通常,这类工具作为独立软件或作为 ClickHouse 自身的一部分提供。用户可以通过连接到运行在本地或远程服务器上的 ClickHouse 数据库实例,然后通过命令行接口或图形化界面提交 SQL 查询来进行操作。一些第三方工具可能会提供额外的功能,比如自动建议完成、代码片段预览等,进一步提升用户体验。 #### 应用场景 - **学习与教育**:帮助初学者更好地理解 ClickHouse查询机制和优化策略。 - **故障排查**:当查询执行速度慢或出错时,解析工具可以提供深入洞察,协助定位问题所在。 - **性能分析**:通过对执行计划的详细观察,可以深入了解系统资源利用情况,进而调整配置以提高效率。 #### 相关问题: 1. 如何集成第三方 SQL 解析工具以增强 ClickHouse查询调试功能? 2. 在 ClickHouse 中,如何手动控制查询的执行计划以优化性能? 3. ClickHouse SQL 解析工具支持哪些高级特性,例如解释型查询、批处理查询等?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shangjg3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值