第四章 查询优化

一、单表查询
  1. Prewhere 替代 where
    Prewhere 和 where 语句的作用相同,用来过滤数据。不同之处在于 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. 数据采样
    通过采样运算可极大提升数据分析的性能,采样修饰符只有在 MergeTree engine 表中才有效,且在创建表时需要指定采样策略。
SELECT Title,count(*) AS PageViews 
FROM hits_v1
SAMPLE 0.1 #代表采样 10%的数据,也可以是具体的条数
WHERE CounterID =57
GROUP BY Title
ORDER BY PageViews DESC LIMIT 1000
  1. 列裁剪与分区裁剪
    数据量太大时应避免使用 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. 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. 避免构建虚拟列
    如非必须,不要在结果集上构建虚拟列,虚拟列非常消耗资源浪费性能,可以考虑在前端进行处理,或者在表中构造实际字段进行额外存储。
反例:
SELECT Income,Age,Income/Age as IncRate FROM datasets.hits_v1;

正例:拿到 Income 和 Age 后,考虑在前端进行处理,或者在表中构造实际字段进行额外存储
SELECT Income,Age FROM datasets.hits_v1;
  1. 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. 使用物化视图
  2. 查询熔断
    为了避免因个别慢查询引起的服务雪崩的问题,除了可以为单个查询设置超时以外,还可以配置周期熔断,在一个查询周期内,如果用户频繁进行慢查询操作超出规定阈值后将无法继续进行查询操作。
  3. 关闭虚拟内存
    物理内存和虚拟内存的数据交换,会导致查询变慢,资源允许的情况下关闭虚拟内存。
  4. 配置 join_use_nulls
    为每一个账户添加 join_use_nulls 配置,左表中的一条记录在右表中不存在,右表的相应字段会返回该字段相应数据类型的默认值,而不是标准 SQL 中的 Null 值。
  5. 批量写入时先排序
    批量写入数据时,必须控制每个批次的数据中涉及到的分区的数量,在写入之前最好对需要导入的数据进行排序。无序的数据或者涉及的分区太多,会导致 ClickHouse 无法及时对新导入的数据进行合并,从而影响查询性能。
  6. 关注 CPU
    cpu 一般在 50%左右会出现查询波动,达到 70%会出现大范围的查询超时,cpu 是最关键的指标,要非常关注。
二、多表关联
  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;
  1. 用 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;
  1. 大小表 JOIN
    多表 join 时要满足小表在右的原则,右表关联时被加载到内存中与左表进行比较,ClickHouse 中无论是 Left join 、Right join 还是 Inner join 永远都是拿着右表中的每一条记录到左表中查找该记录是否存在,所以右表必须是小表。
  2. 注意谓词下推(版本差异)
    ClickHouse 在 join 查询时不会主动发起谓词下推的操作,需要每个子查询提前完成过滤操作,需要注意的是,是否执行谓词下推,对性能影响差别很大(新版本中已经不存在此问题,但是需要注意谓词的位置的不同依然有性能的差异)
  3. 分布式表使用GLOBAL
    两张分布式表上的 IN 和 JOIN 之前必须加上 GLOBAL 关键字,右表只会在接收查询请求的那个节点查询一次,并将其分发到其他节点上。如果不加 GLOBAL 关键字的话,每个节点都会单独发起一次对右表的查询,而右表又是分布式表,就导致右表一共会被查询 N²次(N是该分布式表的分片数量),这就是查询放大,会带来很大开销。
  4. 使用字典表
    将一些需要关联分析的业务创建成字典表进行 join 操作,前提是字典表不宜太大,因为字典表会常驻内存。
  5. 提前过滤
    通过增加逻辑过滤可以减少数据扫描,达到提高执行速度及降低内存消耗的目的。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容