ClickHouse優化典藏
-
建表時能用數值型或日期時間型表示的字段就不要用字符串,全String類型在以Hive為中心的數倉建設中常見,但ClickHouse環境不應受此影響。
-
雖然ClickHouse底層將DateTime存儲為時間戳Long類型,但不建議存儲Long類型,因為DateTime不需要經過函數轉換處理,執行效率高、可讀性好。
-
官方已經指出Nullable類型幾乎總是會拖累性能,因為存儲Nullable列時需要創建一個額外的文件來存儲NULL的標記,並且Nullable列無法被索引。因此除非極特殊情況,應直接使用字段默認值表示空,或者自行指定一個在業務中無意義的值(例如用-1表示沒有商品ID)。
1.2 分區和索引
-
分區粒度根據業務特點決定,不宜過粗或過細。一般選擇按天分區,也可以指定為Tuple(),以單表一億數據為例,分區大小控制在10-30個為最佳。
-
必須指定索引列,ClickHouse中的索引列即排序列,通過order by指定,一般在查詢條件中經常被用來充當篩選條件的屬性被納入進來;可以是單一維度,也可以是組合維度的索引;通常需要滿足高級列在前、查詢頻率大的在前原則;還有基數特別大的不適合做索引列,如用戶表的userid字段;通常篩選后的數據滿足在百萬以內為最佳。
-
Index_granularity是用來控制索引粒度的,默認是8192,如非必須不建議調整。
-
如果表中不是必須保留全量歷史數據,建議指定TTL(生存時間值),可以免去手動過期歷史數據的麻煩,TTL 也可以通過alter table語句隨時修改。
- 使用Prewhere替代where,當查詢列明顯多於篩選列時使用Prewhere可十倍提升查詢性能,Prewhere會自動優化執行過濾階段的數據讀取方式,降低io操作。
# Prewhere會自動優化執行過濾階段的數據讀取方式,降低io操作
select * from school where class='xxxx' and (id='11' or code='doctor')
# 替換where關鍵字
select * from school prewhere class='xxxx' and (id='11' or code='doctor')
-
數據采樣,通過采用運算可極大提升數據分析的性能
采樣修飾符只有在MergeTree engine表中才有效,且在創建表時需要指定采樣策略。SELECT Title,count(*) 10 AS PageViews
FROM hits_distributed
SAMPLE 0.1 #代表采樣10%的數據,也可以是具體的條數
WHERE CounterlD =34
GROUP BY Title
ORDER BY PageViews DESC LIMIT 1000
-
數據量太大時應避免使用select *操作,查詢的性能會與查詢的字段大小和數量成線性表換,字段越少,消耗的io資源越少,性能就會越高。
反例:
select * from test.test;
正例:
select login_id,name,sex from test.test;
-
千萬以上數據集進行order by 查詢時需要搭配where 條件和limit語句一起使用。
-
如非必須不要在結果集上構建虛擬列,虛擬列非常消耗資源浪費性能,可以考慮在前端進行處理,或者子啊表中構造實際字段進行額外存儲。
-
使用uniqCombined替代distinct性能可提升10倍以上,uniqCombined底層采用類似HyperLogLog算法實現,能接收2%左右的數據誤差,可直接使用這種去重方式提升查詢性能。
-
對於一些確定的數據模型,可將統計指標通過物化視圖的方式進行構建,這樣可避免查詢時重復計算的過程,物化視圖會在有新數據插入時進行更新。
#通過物化視圖提前預計算用戶下載量
CREATE MATERIALIZED VIEW download hour_mv
ENGINE =SummingMergeTree
PARTITION BY toYYYYMM(hour) ORDER BY (userid, hour)
AS SELECT
toStartOfHour(when) AS hour,
userid,
count() as downloads,
sum(bytes) AS bytes
FROM download WHERE when >= toDateTime (2021-01-01 00:00:00") #設置更新點,該時間點之前的數據可以通過insert intoselect的方式進行插入
GROUP BY userid, hour
##或者
CREATE MATERIALIZED VIEW db.table_MV TO db.table new ##table new可以是一張mergetree表
AS SELECT FROM db.table old;
#不建議添加populate關鍵字進行全量更新
- 不建議在高基列上執行distinct去重查詢,改為近似去重uniqCombined
反例:
SELECT count( DISTINCT create_user ) from app.scene_model
正例:
SELECT uniqCombined( create_user ) from app.scene_model
2.2 多表關聯
- 當多表聯查是,查詢的數據僅從其中一張表出時,可考慮用 IN 操作而不是JOIN
select a.* from a where a.uid in (select uid from b)
#不要寫成
select a.* from a left join b on a.uid=b.uid
- 多表join時要滿足小表在右的原則,右表關聯時被加載到內存中與左表進行比較,ClickHouse中無論是Left join 、Right join 還是 Inner join 永遠都是拿着右表中的每一條記錄到左表中查找該記錄是否存在,所以右表必須是小表。
- ClickHouse在join查詢時不會主動發起謂詞下推的操作,需要每個子查詢提前完成過濾操作,需要注意的是,是否執行謂詞下推,對性能影響差別很大(新版本中已經不存在此問題,但是需要注意謂詞的位置的不同依然有性能的差異)
- 將一些需要關聯分析的業務創建成字典表進行join操作,前提是字典表不易太大,因為字典表會常駐內存
ENGINE = Dictionary (dict name)
或者
create database db dicENGINE = Dictionary
-
通過增加邏輯過濾可以減少數據掃描,達到提高執行速度及降低內存消耗的目的
-
盡量不要執行單條或小批量刪除和插入操作,這樣會產生小分區文件,給后台Merge任務帶來巨大壓力
-
不要一次寫入太多分區,或數據寫入太快,數據寫入太快會導致Merge速度跟不上而報錯,一般建議每秒鍾發起2-3次寫入操作,每次操作寫入2w~5w條數據(依服務器性能而定)
處理方式:報錯信息
1. Code: 252, e.displayText() = DB::Exception: Too many parts(304). Merges are processing significantly slower than inserts
2. Code: 241, e.displayText() = DB::Exception: Memory limit (for query) exceeded:would use 9.37 GiB (attempt to allocate chunk of 301989888 bytes), maximum: 9.31 GiB
-
“ Too many parts 處理 ” 詳情請點擊:ClickHouse新功能之WAL
- 在服務器內存充裕的情況下增加內存配額,一般通過max_memory_usage來實現
- 在服務器內存不充裕的情況下,建議將超出部分內容分配到系統硬盤上,但會降低執行速度,一般通過max_bytes_before_external_group_by、max_bytes_before_external_sort參數來實現
配置 | 描述 |
backgroup_pool_size | 后台線程池的大小,merge線程就是在該線程池中執行,該線程池不僅僅是給merge線程用的,默認值16,允許的前提下建議改成cpu個數的2倍。 |
log_queries | 默認值為0,修改為1,系統會自動創建system_query_log表,並記錄每次查詢的query信息 。 |
max_execution_time | 設置單次查詢的最大耗時時,單位是秒;默認無限制;需要注意的是客戶端的超時設置會覆蓋該參數 |
max_concurrent_queries | 最大並發處理的請求數(包含select,insert等),默認值100,推薦150(不夠再加)。 |
max_threads | 設置單個查詢所能使用的最大cpu個數,默認是cpu核數 |
max_memory_usage | 此參數在config.xml 中,表示單次Query占用內存最大值,該值可以設置的比較大,這樣可以提升集群查詢的上限。 |
max_bytes_before_external_group_by | 一般按照max_memory_usage的一半設置內存,當group使用內存超過閾值后會刷新到磁盤進行。 |
max_bytes_before_external_sort | 當order by已使用max_bytes_before_external_sort內存就進行溢寫磁盤(基於磁盤排序),如果不設置該值,那么當內存不夠時直接拋錯,設置了該值order by可以正常完成,但是速度相對存內存來說肯定要慢點(實測慢的非常多,無法接受)。 |
max_table_size_to_drop | 此參數在 config.xml 中,應用於需要刪除表或分區的情況,默認是50GB,意思是如果刪除50GB以上的分區表會失敗。建議修改為0,這樣不管多大的分區表都可以刪除。 |
新版clickhouse提供了一個實驗性的功能,就是講ClickHouse偽裝成一個MySQL的備庫去實時拉取MySQL中的數據,當MySQL庫表數據發生變化時會實時同步導入ClickHouse中,這樣就省掉了單獨維護實時spark/flink任務讀取kafka數據再存入clickhouse的環節,大大降低了運維成本提升了效率。
CREATE DATABASE test ENGINE =MaterializeMySQL('192.168.0.88:3306', 'ck_test','root', '123")
ClickHouse的數據導入,數據同步消耗的時間是數據計算時間的十幾倍,通過以下導入方式再加上緩存、批處理等機制封裝成新的同步工具,提高同步效率。
catfilename.orc |clickhouse-client --query="INSERT INTO test FORM AT ORC"
4.4 查詢熔斷
為了避免因個別慢查詢引起的服務雪崩的問題,除了可以為單個查詢設置超時以外,還可以配置周期熔斷,在一個查詢周期內,如果用戶頻繁進行慢查詢操作超出規定閾值后將無法繼續進行查詢操作。
五、 其他優化-
關閉虛擬內存,物理內存和虛擬內存的數據交換,會導致查詢變慢。
-
為每一個賬戶添加 join_use_nulls 配置,左表中的一條記錄在右表中不存在,右表的相應字段會返回該字段相應數據類型的默認值,而不是標准SQL中的Null值。
-
批量寫入數據時,必須控制每個批次的數據中涉及到的分區的數量,在寫入之前最好對需要導入的數據進行排序。無序的數據或者涉及的分區太多,會導致ClickHouse無法及時對新導入的數據進行合並,從而影響查詢性能。
-
盡量減少JOIN時的左右表的數據量,必要時可以提前對某張表進行聚合操作,減少數據條數。有些時候,先group by再join比先join再group by查詢時間更短。
-
ClickHouse的分布式表性能性價比不如物理表高,建表分區字段值不宜過多,防止數據導入過程磁盤可能會被打滿。
-
cpu一般在50%左右會出現查詢波動,達到70%會出現大范圍的查詢超時,cpu是最關鍵的指標,要非常關注。
-