ClickHouse-查詢優化

單表查詢【使用的頻率高】

1.prewhere代表where

Prewhere 和 where 語句的作用相同,用來過濾數據。不同之處在於 prewhere 只支持*MergeTree 族系列引擎的表,首先會讀取指定的列數據,來判斷數據過濾,等待數據過濾之後再讀取 select 聲明的列字段來補全其餘屬性。
當查詢列明顯多於篩選列時使用 Prewhere 可十倍提升查詢性能,Prewhere 會自動優化執行過濾階段的數據讀取方式,降低 io 操作。
在某些場合下,prewhere 語句比 where 語句處理的數據量更少性能更高

關閉where自動轉prewhere

set optimize_move_to_prewhere=0;

使用where的查詢:

152 rows in set. Elapsed: 0.691 sec. Processed 8.87 million rows, 3.86 GB (12.83 million rows/s., 5.58 GB/s.)

使用prewhere的查詢:

152 rows in set. Elapsed: 0.050 sec. Processed 8.87 million rows, 112.14 MB (176.21 million rows/s., 2.23 GB/s.)

可明顯看到使用prewhere掃描的數據更少,耗費的時間更短。

默認情況,我們肯定不會關閉 where 自動優化成 prewhere,但是某些場景即使開啓優化,也不會自動轉換成 prewhere,需要手動指定 prewhere:
A.使用常量表達式
B.使用默認值爲 alias 類型的字段
C.包含了 arrayJOIN,globalIn,globalNotIn 或者 indexHint 的查詢
D.select 查詢的列字段和 where 的謂詞相同
E.使用了主鍵字段

2. 數據採樣

通過採樣運算可極大提升數據分析的性能

SELECT
    Title,
    count(*) AS PageViews
FROM datasets.hits_v1
SAMPLE 1 / 10
WHERE CounterID = 57
GROUP BY Title
ORDER BY PageViews DESC
LIMIT 1000

SAMPLE 0.1 :
代表採樣 10%的數據,也可以是具體的條數
採樣修飾符只有在 MergeTree engine 表中才有效,且在創建表時需要指定採樣策略。

3. 列裁剪與分區裁剪

數據量太大時應避免使用 select * 操作,查詢的性能會與查詢的字段大小和數量成線性表換,字段越少,消耗的 io 資源越少,性能就會越高。

分區裁剪就是隻讀取需要的分區,在過濾條件中指定。

4. order by結合where、limit

千萬以上數據集進行 order by 查詢時需要搭配 where 條件和 limit 語句一起使用。

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

5. 避免構建虛擬列

如非必須,不要在結果集上構建虛擬列,虛擬列非常消耗資源浪費性能,可以考慮在前端進行處理,或者在表中構造實際字段進行額外存儲。

反例:

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

拿到 Income 和 Age 後,考慮在前端進行處理,或者在表中構造實際字段進行額外存儲
SELECT Income,Age FROM datasets.hits_v1;

6. uniqCombined替代distinct

性能可提升 10 倍以上,uniqCombined 底層採用類似 HyperLogLog 算法實現,能接收 2%左右的數據誤差,可直接使用這種去重方式提升查詢性能。Count(distinct )會使用 uniqExact精確去重。
不建議在千萬級不同數據上執行 distinct 去重查詢,改爲近似去重 uniqCombined

SELECT uniqCombined(rand()) from datasets.hits_v1
 

其他

1)查詢熔斷

爲了避免因個別慢查詢引起的服務雪崩的問題,除了可以爲單個查詢設置超時以外,還可以配置週期熔斷,在一個查詢週期內,如果用戶頻繁進行慢查詢操作超出規定閾值後將無法繼續進行查詢操作

2)關閉虛擬內存

物理內存和虛擬內存的數據交換,會導致查詢變慢,資源允許的情況下關閉虛擬內存

3)配置join_use_nulls

爲每一個賬戶添加 join_use_nulls 配置,左表中的一條記錄在右表中不存在,右表的相應字段會返回該字段相應數據類型的默認值,而不是標準 SQL 中的 Null 值

4)批量寫入時先排序
批量寫入數據時,必須控制每個批次的數據中涉及到的分區的數量,在寫入之前最好對需要導入的數據進行排序。無序的數據或者涉及的分區太多,會導致 ClickHouse 無法及時對新導入的數據進行合併,從而影響查詢性能。

5)關注CPU
cpu 一般在 50%左右會出現查詢波動,達到 70%會出現大範圍的查詢超時,cpu 是最關鍵的指標,要非常關注。

多表關聯

1. 用IN代替JOIN

當多表聯查時,查詢的數據僅從其中一張表出時,可考慮用 IN 操作而不是 JOIN

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

2. 大小表join

多表 join 時要滿足小表在右的原則,右表關聯時被加載到內存中與左表進行比較,ClickHouse 中無論是 Left join 、Right join 還是 Inner join 永遠都是拿着右表中的每一條記錄到左表中查找該記錄是否存在,所以右表必須是小表。

3. 謂詞下推

ClickHouse 在 join 查詢時不會主動發起謂詞下推的操作,需要每個子查詢提前完成過濾操作,需要注意的是,是否執行謂詞下推,對性能影響差別很大(新版本中已經不存在此問題,但是需要注意謂詞的位置的不同依然有性能的差異)

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

耗時:9s

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;

耗時:7s

4.分佈式表使用GLOBAL

兩張分佈式表上的 IN 和 JOIN 之前必須加上 GLOBAL 關鍵字,右表只會在接收查詢請求的那個節點查詢一次,並將其分發到其他節點上。如果不加 GLOBAL 關鍵字的話,每個節點都會單獨發起一次對右表的查詢,而右表又是分佈式表,就導致右表一共會被查詢 N²次(N是該分佈式表的分片數量),這就是查詢放大,會帶來很大開銷。

5. 使用字典表

將一些需要關聯分析的業務創建成字典表進行 join 操作,前提是字典表不宜太大,因爲字典表會常駐內存

6. 提前過濾

通過增加邏輯過濾可以減少數據掃描,達到提高執行速度及降低內存消耗的目的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章