clickhouse使用入門(clickhouse使用教程)
導語:同學,你也不想你根本不懂ClickHouse,卻趕鴨子上架使用的事情被其他人知道吧?
寫在前面:本文旨在讓原先有一定SQL基礎的人快速簡單了解ClickHouse的(關鍵)概念/特性,側重于使用方面的介紹比較而非原理/實現挖掘。文章算是個人摘錄學習 理解,主要參考資料為ClickHouse官方(英文)文檔(畢竟絕對權威),寫于2023年5月,請注意時效性。
簡要介紹
ClickHouse是一個用于聯機分析處理(OLAP)的列式數據庫管理系統(tǒng)(DBMS),擁有著及其卓越的查詢速度。OLAP是一種面向分析的處理,用于處理大量的數據并支持復雜的分析和查詢操作。諸如BI系統(tǒng)等重數據分析的場景,都應重點考慮使用OLAP數據庫,而其中ClickHouse又是OLAP數據庫星海中最璀璨的一顆星。
ClickHouse的場景特點
縱使ClickHouse有千般萬好,但是能真正契合系統(tǒng)需求的數據庫,才是最合適的。因此,在我們正式邁進ClickHouse使用大門之前,我想有必要先識其長短。
上圖截自ClickHouse官方文檔,與其說這是olap的場景,不妨說是ClickHouse的常見場景。其中我覺得有必要指出的是:
1.”查詢相對較少”,這意味著ClickHouse并發(fā)查詢能力不強(官方建議每秒最多查詢100次),原因在于對于每條查詢,ClickHouse都會盡可能動用服務器的CPU、內存資源等,而不同于MySQL單條SQL是單線程的,資源消耗更不可控(當然ClickHouse本身也有相關參數可以配置查詢消耗的資源情況)。
2.”結果適合于單個服務器的RAM中”,結合上面所說,每條查詢都會消耗ClickHouse不少的(內存)資源,因此不要無腦join大表,否則Memorylimitexceeded警告。
在開始更有意義的贊美之前,讓我再對ClickHouse進行一些”自由的批評”:
1.盡管ClickHouse與mysql等數據庫一樣支持標準SQL語法(甚至兼容了mysql的G語法)以及窗口函數等,但是相關子查詢暫未支持,但將來會實現。
2.稀疏索引使得ClickHouse不適合通過其鍵檢索單行的點查詢。(稀疏索引只存儲非零值,因此在進行點查詢時需要遍歷整個索引才能找到對應的行,這會導致點查詢的性能較低)
ClickHouse基礎
連接及數據格式
連接方式
ClickHouse提供了HTTP和TCP以及gRPC三種方式的接口,非常方便,其中ClickHouse-client是基于TCP方式的,不同的client和服務器版本彼此兼容。
以HTTP接口方式訪問時,需注意使用GET方法請求時是默認readonly的。換句話說,若要作修改數據的查詢,只能使用POST方法。
此外,除了上述的接口形式,ClickHouse甚至支持了MySQL wire通訊協(xié)議,生怕像我一樣的MySQL boy難以上手。簡單的配置之后,就能輕松使用mysqlclient連接ClickHouse服務器,頗有import pytorch as tf之感(這何嘗不是一種語言層面的ntr)。不過也有一些限制,不支持prepared查詢以及某些數據類型會以字符串形式發(fā)送。同樣命運的還有PostgreSQL。
當然,更常見的使用方式還是各語言實現的client庫。如今ClickHouse的生態(tài)早已成熟,無論是各類編程語言亦或是常見的InfrastructureProducts(怎么翻都別扭干脆貼原文,后同)(如kafka、k8s、grafana等),都有現成的庫將其結合起來使用。
數據格式
ClickHouse支持豐富的輸入/輸出格式,簡單來說就是TSV、CSV、JSON、XML、Protobuf、二進制格式以及一些Hadoop生態(tài)下常見的數據格式。此外ClickHouse本身也有一些模式推斷相關的函數,能從文件/hdfs等數據源推斷出表的結構,算是個有趣的功能。
數據類型
常用的:
整型:追求極致性能的ClickHouse,自然是會在字節(jié)維度上錙銖必較的,整型類型的可選范圍為(U)Int8到Int256,當然講究兼容的ClickHouse也是允許你定義BIGINT、BOOL、INT4之類的,會對應到相應的字節(jié)數類型上。什么,你還要像mysql那樣定義展示寬度(11)?對不起,做不到.jpg。
浮點數:Float32?FLOAT、Float64?DOUBLE,需注意計算可能出現Inf和NaN。
Bool:內部等同于UInt8。
String:字節(jié)數沒有限制,與LONGTEXT,MEDIUMTEXT,TINYTEXT,TEXT,LONGBLOB,MEDIUMBLOB,TINYBLOB,BLOB,VARCHAR,CHAR同義。
Date:取值范圍[1970-01-01,2149-06-06](當前)。
DateTime:具體到秒的時間??梢灾付〞r區(qū),如DateTime('Asia/Shanghai'),如不指定將使用ClickHouse服務器的時區(qū)設置。
時區(qū)僅用作以文本形式輸入輸出數據時的轉換(所以時區(qū)函數是沒有計算cost的),實際以unix timestamp存儲。因此,如果插入數據時寫211046400和1976-09-09 00:00:00是等效的(時區(qū)為東八區(qū)的話)。
array:定義方式為array(T),下標起始為1,可以定義多維數組。數組元素最大可為一百萬個。數組內的元素類型需兼容,不兼容將拋出異常??赏ㄟ^sizeN-1快速獲得對應第N維的長度。
Tuple:定義方式為Tuple(arg1 type1,arg2 type2…)。后續(xù)可通過類似a.b的方式獲取對應的值。元組間的比較為依次比較各元素大小。
Nullable:可用Nullable修飾一個類型,使其允許包含NULL值,代價是,被修飾的列無法作為表的索引項。同時,為了存儲Nullable值,ClickHouse還會額外使用一個帶有NULL掩碼的文件來區(qū)分列的默認值與NULL值,會在存儲空間以及性能上造成額外負擔。
也正是因為特殊對待了Nullable的字段,可以用`字段名`.null(這個值將返回1或0標識是否為空值)快速找到對應字段為null的行。
總之,能用業(yè)務邏輯來區(qū)分空值,就盡量不要定義Nullable字段。
AggregateFunction:黑魔法,用法是AggregateFunction(func,types_of_argument..),如AggregateFunction(uniq,UInt64)。目前只支持uniq,anyIf和quantiles聚合函數。
可以配合xx-State函數得到中間狀態(tài),通過xx-Merge函數得到結果。好處就是可以將計算狀態(tài)序列化到表里,減少數據存儲量。通常是通過物化視圖實現的。
SimpleAggregateFunction:類似于AggregateFunction類型,支持更多的聚合函數,且無需應用xx-Merge和xx-State函數來得到值。
不常用的(我覺得):
Decimal:
P-精度。有效范圍:[1:76],決定可以有多少個十進制數字(包括分數)。
S-規(guī)模。有效范圍:[0:P],決定數字的小數部分中包含的小數位數。
FixedString(N):顧名思義,需注意N為字節(jié),當字段的字節(jié)數剛好與指定的N相等時最高效,適合存一些明確的枚舉。超過會拋出異常。
UUID:配合generateUUIDv4函數食用更佳。
Date32:范圍為有符號32位整數,表示相對1970-01-01的的天數。
DateTime64:時間范圍[1900-01-01 00:00:00,2299-12-31 23:59:59.99999999]。但不同于DateTime會與String自動轉換,需借助諸如toDateTime64之類的時間處理函數。
枚舉:有Enum8和Enum16兩種類型,將預定字符串與整型數字關聯。插入枚舉值之外的值將拋出異常,枚舉值不能直接跟數字作比較。
LowCardinality:用法是LowCardinality(data_type),data_type的可選類型為String,FixedString,Date,DateTime及除Decimal外的數字類型。
即將所在列的不同值映射到一個較短的編碼,當少于10000個不同的值時ClickHouse可以進行更高效的數據存儲和處理。比枚舉類型有更高的性能和靈活性。
域(Domain):域是出于使用戶易用等目的,在不修改原類型底層表示的情況下為基礎類型添加了部分特性的類型,用戶不能自定義域。目前有IPV4和IPV6兩個類型,用途可顧名思義。
Nested:定義方式為Nested(name1Type1,Name2Type2,…),如DistrictNested(ProvinceString,CityString),后續(xù)就可以通過District.City訪問具體值,將得到數組對象。(重生之我在DB定義結構體)
flatten_nested設為0(非默認值)可以無限套娃Nested類型。ALTER命令操作Nested類型會受限。
地理位置:包含了Point、Ring、Polygon、MultiPolygon四種類型,即Tuple(Float64,Float64),Array(Point),Array(Ring),Array(Polygon)。其中Polygon的表示方式為首元素為最外層輪廓的點集合,其余元素視作多邊形的”洞”。
字典:定義方式Map(key,value),key可為String,Integer,LowCardinality,FixedString,UUID,Date,DateTime,Date32,Enum,value類型任意,包括Map本身。取數時寫法也與各大編程語言相同,當key不存在時默認返回類型的零值,也支持a.keys和a.values這樣的語法。(Re:從零開始的異世界DB寫Map生活)
SQL語句
ClickHouse支持的SQL語句如上所示,內容太多了。。只簡單挑些重點看下,先留個坑。
SELECT
小技巧:
select取最終列時,可以使用COLUMNS表達式來以re2的正則表達式語法查找匹配的列,如COLUMNS(‘a’)可以匹配aa,ab列,效果類似python的re.search方法,查詢大寬表的時,這個功能還是非常好用的。
此外,配合APPLY(<func>),EXCEPT(col_name..),REPLACE(<expr>ascol_name)這三個語法糖,有時能大大簡化SQL,如:
SELECT COLUMNS(‘_w’) EXCEPT(‘test’) APPLY(max) from my_table
就能迅速找出帶_w且不帶test的列,并計算他們的最大值。(想想有時只需要簡單分析部分列,卻要施法吟唱半天)
有時需要對單獨某個查詢設置特殊配置時,也可在語句最后直接加上SETTINGS xx,這樣配置就只會對本次查詢生效。
ARRAY JOIN:
用于生成一個新表,該表具有包含該初始列中的每個單獨數組元素的列,而其他列的值將被重復顯示。單行變多行的經典操作??諗到M將不包含在結果中,LEFT ARRAY JOIN則會包含。
可同時ARRAY JOIN多個數組,這種情況下得到的結果并非笛卡爾積。也可以ARRAY JOIN Nested類型。
DISTINCT:
如果需要只對某幾列去重,需用DISTINCTON(column1,column2..),否則視作對全部列去重。DISTINCT子句是先于ORDER BY子句執(zhí)行的。
與不使用聚合函數而對某些列進行GROUPBY相比,結果一般是相同的,但使用DISTINCT時,已處理的數據塊會立馬輸出,而無需等待整個查詢執(zhí)行完成。
INTERSECT、UNION、EXCEPT:
將兩個查詢進行交并補,列數等信息需匹配。重復行多時INTERSECT DISTINCT效果更好。
FROM:
可在數據源名后加上FINAL修飾符,ClickHouse會在返回結果之前完全合并數據,從而執(zhí)行給定表引擎合并期間發(fā)生的所有數據轉換。只適用于MergeTree-引擎族。使用FINAL修飾符的SELECT查詢啟用了并發(fā)執(zhí)行,但仍比不帶FINAL的查詢更慢,一是因為這會在查詢執(zhí)行過程中合并數據,二是FINAL會額外讀取主鍵列。多數情況下不推薦使用,通??梢酝ㄟ^假設MergeTree的后臺進程還未生效(引擎部分再談),并使用聚合函數來達到同樣效果。
此外不同于很多數據庫在你缺失相關參數時給個錯誤,ClickHouse在很多地方都做了默認參數的設置。比如在你不指定FROM子句時,默認從system.one表查詢,以及支持select count()(會傾向于選取最小的列進行計數)這樣的寫法。不過這好不好嘛,還是智者見智仁者見仁,在不理解的情況下被暗戳戳地坑一把也是可能的。
Join:
除了支持標準的SQL JOIN類型,還支持ASOF JOIN,常用于根據時間序列不完全匹配地join多個表,比如用來匹配用戶事件活動記錄。
涉及到分布式表的join:
當使用普通JOIN時,將查詢發(fā)送到遠程服務器。在每個服務器上單獨形成右表。
當使用GLOBAL … JOIN時,首先請求者服務器運行一個子查詢來計算正確的表。此臨時表將傳遞到每個遠程服務器,并使用傳輸的臨時數據對其運行查詢。
當運行JOIN操作時,與查詢的其他階段相比,執(zhí)行順序沒有進行優(yōu)化。JOIN操作會在WHERE過濾和聚合之前運行。
同樣的join操作在子查詢中又會再次執(zhí)行一次,要避免這種情況可以考慮使用Join這個表引擎。
默認情況下,ClickHouse使用哈希聯接算法。 ClickHouse取右表并在內存中為其創(chuàng)建哈希表。(所以一個很重要的最佳實踐是join表時把小表放在右表)在達到某個內存消耗閾值后,ClickHouse會回退到合并聯接算法。
INSERT INTO
插入數據時會對寫入的數據進行一些處理,按照主鍵排序,按照分區(qū)鍵對數據進行分區(qū)等。所以如果在寫入數據中包含多個分區(qū)的混合數據時,將會顯著的降低INSERT的性能。為了避免這種情況:
- 數據總是以盡量大的batch進行寫入,如每次寫入100,000行。
- 數據在寫入ClickHouse前預先的對數據進行分組。
在以下的情況下,性能不會下降:
- 數據總是被實時的寫入。
- 寫入的數據已經按照時間排序。
也可以異步的、小規(guī)模的插入數據,這些數據會被合并成多個批次,然后安全地寫入到表中。這是通過設置async_insert來實現的,異步插入的方式只支持HTTP協(xié)議,并且不支持數據去重。
CREATE
Materialized(物化視圖)
創(chuàng)建語法:
CREATE MATERIALIZED VIEW [IF NOT exists] [db.]table_name [ON CLUSTER] [TO[db.]name] [ENGINE = engine] [POPULATE] AS SELECT …
創(chuàng)建不帶TO [db].[table]的物化視圖時,必須指定ENGINE–用于存儲數據的表引擎。
使用TO [db].[table]創(chuàng)建物化視圖時,不得使用POPULATE。
具體實現:當向SELECT中指定的表插入數據時,插入數據的一部分被這個SELECT查詢轉換,結果插入到視圖中。
ClickHouse 中的物化視圖更像是插入觸發(fā)器。 如果視圖查詢中有一些聚合,則它僅應用于一批新插入的數據。對源表現有數據的任何更改(如更新、刪除、刪除分區(qū)等)都不會更改物化視圖。
ClickHouse 中的物化視圖在出現錯誤時沒有確定性行為。這意味著已經寫入的塊將保留在目標表中,但出現錯誤后的所有塊則不會寫入。
如果指定POPULATE,則在創(chuàng)建視圖時將現有表數據插入到視圖中,就像創(chuàng)建一個CREATE TABLE … AS SELECT …一樣。否則,查詢僅包含創(chuàng)建視圖后插入表中的數據。不建議使用POPULATE,因為在創(chuàng)建視圖期間插入表中的數據不會插入其中。
SELECT查詢可以包含DISTINCT、GROUP BY、ORDER BY、LIMIT……請注意,相應的轉換是在每個插入數據塊上獨立執(zhí)行的。 例如,如果設置了GROUP BY,則在插入期間聚合數據,但僅在插入數據的單個數據包內。數據不會被進一步聚合。例外情況是使用獨立執(zhí)行數據聚合的ENGINE,例如SummingMergeTree。
在物化視圖上執(zhí)行ALTER查詢有局限性,因此可能不方便。如果物化視圖使用構造TO [db.]name,你可以DETACH視圖,為目標表運行ALTER,然后ATTACH先前分離的視圖。
視圖看起來與普通表相同。 例如,它們列在SHOW TABLES查詢的結果中。
ALTER
UPDATE
沒錯,update操作被置于ALTER操作下,這意味著ClickHouse的update操作不像oltp數據庫那般輕量級,應盡量避免使用。是通過mutation來實現的。
Mutations(突變)
用來操作表數據的ALTER查詢是通過一種叫做“突變”的機制來實現的,最明顯的是ALTER TABLE … DELETE和ALTER TABLE … UPDATE。它們是異步的后臺進程,類似于MergeTree表的合并,產生新的“突變”版本的數據part(后面會詮釋這個概念)。
對于*MergeTree表,通過重寫整個數據part來執(zhí)行突變。沒有原子性——一旦突變的part準備好,part就會被替換,并且在突變期間開始執(zhí)行的SELECT查詢將看到來自已經突變的part的數據,以及來自尚未突變的part的數據。
突變完全按照它們的產生順序排列,并按此順序應用于每個part。突變還與“INSERT INTO”查詢進行排序:在提交突變之前插入表中的數據將被突變,而在此之后插入的數據將不會被突變。注意,突變不會以任何方式阻止插入。
突變查詢在添加突變條目后立即返回(對于復制表是到ZooKeeper,對于非復制表到文件系統(tǒng))。突變本身使用系統(tǒng)配置文件來配置異步執(zhí)行。要跟蹤突變的進程,可以使用system.mutations表。成功提交的變異將繼續(xù)執(zhí)行,即使ClickHouse服務器重新啟動。沒有辦法回滾突變一旦提交,但如果突變卡住了,可以使用KILL MUTATION阻止突變的執(zhí)行。
完成突變的條目不會立即刪除(保留條目的數量由finished_mutations_to_keep存儲引擎參數決定)。
DELETE
刪除的行會被立即標記為已刪除,并將自動從所有后續(xù)查詢中過濾掉。數據清理在后臺異步發(fā)生。此功能僅適用于 MergeTree 表引擎系列。這就是ClickHouse的輕量級刪除
原理:當執(zhí)行DELETE時,ClickHouse 僅保存一個掩碼,其中每一行都被標記為“現有”或“已刪除”。 掩碼實現為一個隱藏的_row_exists系統(tǒng)列,所有可見行該列存儲為 True,刪除的行存儲為False。僅當一個數據part中部分行被刪除了,這個字段才會出現。
DELETE操作實際上是被翻譯成ALTER TABLE update _row_exists = 0 WHERE …的mutation操作。
引擎
數據庫引擎
Atomic
ClickHouse的默認數據庫引擎,支持非阻塞的DROP TABLE、RENAME TABLE和具有原子性的EXCHANGE TABLE操作。
DROP TABLE時只會將表標記為已刪除,并且把元數據移到/clickhouse_path/metadata_dropped/,然后通知后臺線程稍后刪除,這個延遲時間可指定,也可設為同步刪除。
Lazy
在最后一次訪問之后,只在內存中保存expiration_time_in_seconds秒。只能用于*Log表。它是為存儲許多小的*Log表而優(yōu)化的,對于這些表,訪問之間有很長的時間間隔。
PostgreSQL、MySQL、SQLite
……用于在ClickHouse與上述三種數據庫間交(tou)換(jia)數據。其中不能在MySQL引擎上執(zhí)行RENAME、CREATETABLE和ALTER來修改表的結構。
另外還有幾個實驗性的引擎,不談。
表引擎
表引擎(即表的類型)決定了:
- 數據的存儲方式和位置,寫到哪里以及從哪里讀取數據
- 支持哪些查詢以及如何支持。
- 并發(fā)數據訪問。
- 索引的使用(如果存在)。
- 是否可以執(zhí)行多線程請求。
- 數據復制參數。
MergeTree系列
MergeTree系列的引擎是ClickHouse中最核心的引擎,提供了列式存儲、自定義分區(qū)、稀疏主鍵索引和二級跳數索引等功能。基于MergeTree的引擎都在部分特定用例下添加了額外的功能,而且通常是在后臺執(zhí)行額外的數據操作來實現的。缺點是這些引擎相對笨重,如果需要許多小表來存一些臨時數據,可以考慮Log系列引擎。
MergeTree
主要特點:
- 存儲按主鍵排序。
- 指定了分區(qū)鍵時,會截取分區(qū)數據,增加查詢效率。
- 支持數據采樣。
完整語句參考:
重要參數說明:
ORDER BY:排序鍵
如果沒有用PRIMARY KEY明確定義主鍵,那么該鍵將被當做主鍵。
如果不需要排序,可以使用ORDERBY tuple()。
排序鍵包含多列時,查詢時走索引依然遵循最左匹配規(guī)則。
PARTITION BY:分區(qū)鍵
大多數情況下,不需要分使用區(qū)鍵。即使需要使用,也不需要使用比月更細粒度的分區(qū)鍵。分區(qū)不會加快查詢(這與ORDER BY表達式不同)。永遠也別使用過細粒度的分區(qū)鍵。
要按月分區(qū),可以使用表達式toYYYYMM(date_column)。
PRIMARY KEY:主鍵
大部分情況下不需要再專門指定一個PRIMARY KEY子句。ClickHouse不要求主鍵唯一。
INDEX:跳數索引
后面介紹。
存儲細節(jié):
不同分區(qū)的數據會被分成不同的片段(part,后同),ClickHouse在后臺合并數據片段以便更高效存儲。
數據片段可以以Wide或Compact格式存儲。在Wide格式下,每一列都會在文件系統(tǒng)中存儲為單獨的文件,在Compact格式下所有列都存儲在一個文件中。Compact格式可以提高插入量少插入頻率頻繁時的性能。
每個數據片段被邏輯的分割成顆粒(granules)。顆粒是ClickHouse中進行數據查詢時的最小不可分割數據集。ClickHouse不會對行或值進行拆分,所以每個顆??偸前麛祩€行。每個顆粒的第一行通過該行的主鍵值進行標記,ClickHouse會為每個數據片段創(chuàng)建一個索引文件來存儲這些標記。對于每列,無論它是否包含在主鍵當中,ClickHouse都會存儲類似標記。
顆粒的大小通過表引擎參數index_granularity(默認8192)和index_granularity_bytes(10Mb)控制。顆粒的行數的在[1,index_granularity]范圍中,這取決于行的大小。如果單行的大小超過了index_granularity_bytes設置的值,那么一個顆粒的大小會超過index_granularity_bytes。在這種情況下,顆粒的大小等于該行的大小。
詳談主鍵與索引:
主鍵的選擇:
稀疏索引使得ClickHouse可以處理極大量的行,因為大多數情況下,這些索引常駐于內存。
長的主鍵會對插入性能和內存消耗有負面影響,但主鍵中額外的列并不影響SELECT查詢的性能。
可以使用ORDER BY tuple()語法創(chuàng)建沒有主鍵的表。在這種情況下ClickHouse根據數據插入的順序存儲。如果在使用INSERT…SELECT時希望保持數據的排序,可以設置max_insert_threads=1。
主鍵與排序鍵不同的情況:
ClickHouse可以做到指定一個跟排序鍵不一樣的主鍵,此時排序鍵用于在數據片段中進行排序,主鍵用于在索引文件中進行標記的寫入。這種情況下,主鍵表達式元組必須是排序鍵表達式元組的前綴。
當使用SummingMergeTree和AggregatingMergeTree引擎時,這個特性非常有用。通常在使用這類引擎時,表里的列分兩種:維度和度量。典型的查詢會通過任意的GROUP BY對度量列進行聚合并通過維度列進行過濾。由于SummingMergeTree和AggregatingMergeTree會對排序鍵相同的行進行聚合,所以把所有的維度放進排序鍵是很自然的做法。但這將導致排序鍵中包含大量的列,并且排序鍵會伴隨著新添加的維度不斷的更新。
在這種情況下合理的做法是,只保留少量的列在主鍵當中用于提升掃描效率,將維度列添加到排序鍵中。
部分單調序列:
如一個月中的天數。它們在一個月的范圍內形成一個單調序列,但如果擴展到更大的時間范圍它們就不再單調了,這就是一個部分單調序列。如果用戶使用部分單調的主鍵創(chuàng)建表,ClickHouse同樣會創(chuàng)建一個稀疏索引。當用戶從這類表中查詢數據時,ClickHouse會對查詢條件進行分析。如果用戶希望獲取兩個索引標記之間的數據并且這兩個標記在一個月以內,ClickHouse可以在這種特殊情況下使用到索引,因為它可以計算出查詢參數與索引標記之間的距離。
如果查詢參數范圍內的主鍵不是單調序列,那么ClickHouse無法使用索引。
ClickHouse在任何主鍵代表一個部分單調序列的情況下都會使用這個邏輯。(這個故事告訴我們?yōu)槭裁茨J主鍵和排序鍵相同)
跳數索引:
示例:INDEX a(u64*i32,s) TYPE minmax GRANULARITY 3。復合列上也能創(chuàng)建。
*MergeTree系列的表可以指定跳數索引。跳數索引是指數據片段按照粒度分割成小塊后,將上述SQL的granularity_value數量的小塊組合成一個大的塊,對這些大塊寫入索引信息,這樣有助于使用where篩選時跳過大量不必要的數據,減少SELECT需要讀取的數據量。
Projection:
投影(projection)類似于物化視圖,但存儲在分區(qū)目錄,即與原表的數據分區(qū)在同一個分區(qū)目錄下??赏ㄟ^投影定義語句SELECT <column list expr> [GROUP BY] <group keys expr> [ORDER BY] <expr>生成。使用可能還需要配置一些參數。
如指定了Group by子句則投影的引擎將變?yōu)锳ggregatingMergeTree,同時所有的聚合函數變?yōu)锳ggregateFunction。指定了ORDER BY子句則會使用對應的key作為主鍵。更多示例可參考:2021年ClickHouse最王炸功能來襲,性能輕松提升40倍。
簡單來說,跟物化視圖的區(qū)別可以看作是——不用再顯式定義一個物化視圖了,對應用層屏蔽了基礎數據和統(tǒng)計數據的區(qū)別。兩類數據你都直接查原表即可。
并發(fā)訪問:
MergeTree引擎也是MVCC(多版本并發(fā)控制)的。
列與表的TTL:
設置TTL即設置數據的過期時間,當列的TTL過期時,ClickHouse會將數據替換成對應數據類型的默認值,當該列所有數據都過期時,該列的數據將會被刪除。(列式數據庫,小子!)主鍵列不可指定。
當表的TTL過期時,過期行會被操作(刪除或轉移),還可通過WHERE和GROUP BY條件指定符合條件的行。GROUP BY表達式必須是表主鍵的前綴。
數據副本
MergeTree系列的引擎的表都支持數據副本,只需在引擎名前加上Replicated。
ReplacingMergeTree
該引擎和MergeTree的不同之處在于它會刪除排序鍵值相同的重復項,適用于在后臺清除重復的數據以節(jié)省空間。但只會在數據合并期間進行,而合并會在后臺一個不確定的時間進行。雖然可以調用OPTIMIZE語句發(fā)起計劃外的合并,但須知OPTIMIZE語句會引發(fā)對數據的大量讀寫。
SummingMergeTree
當合并SummingMergeTree表的數據片段時,ClickHouse會把所有具有相同主鍵的行合并為一行,該行包含了被合并的行中具有數值數據類型的列的sum值。即便如此,當需要聚合數據時仍應該使用sum函數來聚合,因為后臺合并的時間是不確定的。
對于AggregateFunction 類型的列,ClickHouse 根據對應函數表現為AggregatingMergeTree引擎的聚合。
而對于Nested類型的列,ClickHouse會將第一列視作key,其他列視作values進行聚合。
AggregatingMergeTree
將一個數據片段內所有具有相同排序鍵的行替換成一行,這一行會存儲一系列聚合函數的狀態(tài)。引擎使用AggregateFunction和SimpleAggregateFunction類型來處理所有列??梢钥醋鯯ummingMergeTree是AggregatingMergeTree的特化(表現上而言)。
可以使用AggregatingMergeTree表來做增量數據的聚合統(tǒng)計,包括物化視圖的數據聚合。
要插入數據,需使用帶有-State-聚合函數的INSERT SELECT語句。從AggregatingMergeTree表中查詢數據時,需使用GROUP BY子句并且要使用與插入時相同的聚合函數,但后綴要改為-Merge。
CollapsingMergeTree
CollapsingMergeTree 會異步的刪除(折疊)這些除了特定列 Sign 有 1 和 -1 的值以外,其余所有字段的值都相等的成對的行。沒有成對的行將會被保留。
Sign為1和-1的行應按照一定的順序寫入,合并相當取決于記錄的一致性,否則實現不了預期的折疊效果(即先Sign=1后Sign=-1),聚合統(tǒng)計時也應考慮上Sign字段對結果的影響。可以使用Final修飾符強制進行折疊而不聚合,但是效率低下。
此外,插入時Sign=1和Sign=-1的記錄應該在兩次insert語句中分別插入,以保證他們在不同的數據片段(part),否則也不會執(zhí)行合并操作。
個人覺得,難用(其實我想說沒用),或者是我沒找到正確的打開方式。
VersionedCollapsingMergeTree
顧名思義,是上面那位的兄弟,只不過多了一個Version列,允許以多個線程的任何順序插入數據。Version列有助于正確折疊行,即使它們以錯誤的順序插入。
當ClickHouse合并數據部分時,它會刪除具有相同主鍵和版本但Sign值不同的一對行。
當ClickHouse插入數據時,它會按主鍵對行進行排序。 如果Version列不在主鍵中,ClickHouse將其隱式添加到主鍵作為最后一個字段并使用它進行排序。
由于ClickHouse具有不保證具有相同主鍵的所有行都將位于相同的結果數據片段中,甚至位于相同的物理服務器上的特性,以及上面說的數據合并時機的不確定性,所以想要最終的數據還是免不了group by等聚合操作。
GraphiteMergeTree
該引擎用來對Graphite類型數據進行瘦身及匯總。如果不需要對Graphite數據做匯總,那么可以使用任意的表引擎;但若需要,那就采用GraphiteMergeTree引擎。它能減少存儲空間,同時能提高Graphite數據的查詢效率。
Log引擎系列
共同特點:
- 數據存儲在磁盤上。
- 寫入時將數據追加在文件末尾。
- 支持并發(fā)訪問數據時上鎖。(執(zhí)行insert語句時,表會被上寫鎖)
- 不支持突變操作。(參見alter)
- 不支持索引。(表明范圍查詢效率不高)
- 非原子地寫入數據。
各引擎差異:
Log引擎為表中的每一列使用不同的文件。StripeLog將所有的數據存儲在一個文件中。因此StripeLog引擎在操作系統(tǒng)中使用更少的描述符,但是Log引擎提供更高的讀性能。兩者都支持并發(fā)的數據讀取。
TinyLog引擎是該系列中最簡單的引擎并且提供了最少的功能和最低的性能。TinyLog引擎不支持并行讀取和并發(fā)數據訪問,并將每一列存儲在不同的文件中。
Log
Log與TinyLog的不同之處在于,”標記” 的小文件與列文件存在一起。這些標記寫在每個數據塊上,并且包含偏移量,這些偏移量指示從哪里開始讀取文件以便跳過指定的行數。這使得可以在多個線程中讀取表數據。Log引擎適用于臨時數據。
StripeLog
需要寫入許多小數據量(小于一百萬行)的表的場景下使用這個引擎。
寫數據
StripeLog引擎將所有列存儲在一個文件中。對每一次Insert請求,ClickHouse 將數據塊追加在表文件的末尾,逐列寫入。
ClickHouse 為每張表寫入以下文件:
- data.bin— 數據文件。
- index.mrk— 帶標記的文件。標記包含了已插入的每個數據塊中每列的偏移量。
StripeLog引擎不支持ALTER UPDATE和ALTER DELETE操作。
讀數據
帶標記的文件使得 ClickHouse 可以并行的讀取數據。這意味著SELECT請求返回行的順序是不可預測的。
TinyLog
此表引擎通常使用場景:一次寫入數據,然后根據需要多次讀取。
查詢在單個流中執(zhí)行。該引擎適用于相對較小的表(最多約 1,000,000 行)。如果你有很多小表,使用這個表引擎是有意義的,因為它比日志引擎更簡單(需要打開的文件更少)。
與外部系統(tǒng)集成的引擎
正如上面提到的ClickHouse對mysql等數據庫的"支持",實際上在表引擎上也提供了與外部系統(tǒng)的多種集成方式,如下所示。具體不再介紹,有需要可以去官網了解。
其他特殊引擎:
Distributed
分布式引擎本身不存儲數據, 但可以在多個服務器上進行分布式查詢。 讀是自動并行的。讀取時,遠程服務器表的索引(如果有的話)會被使用。
創(chuàng)建語法:
也可使用AS語法使得分布式表指向本地表。
分布式引擎參數
- cluster– 服務為配置中的集群名
- database– 遠程數據庫名
- table– 遠程數據表名
- sharding_key– (可選) 分片key
- policy_name– (可選) 規(guī)則名,它會被用作存儲臨時文件以便異步發(fā)送數據
settings中可進行一些分布式設置。
數據不僅在遠程服務器上讀取,而且在遠程服務器上進行部分處理。例如,對于帶有 GROUP BY的查詢,數據將在遠程服務器上聚合,聚合函數的中間狀態(tài)將被發(fā)送到請求者服務器。然后將進一步聚合數據。
集群:
集群是通過服務器配置文件來配置的。集群名稱不能包含點號。
配置了副本后,讀取操作會從每個分片里選擇一個可用的副本??膳渲秘撦d平衡算法。 如果跟服務器的連接不可用,則會嘗試短超時的重連。如果重連失敗,則選擇下一個副本,依此類推。如果跟所有副本的連接嘗試都失敗,則嘗試用相同的方式再重復幾次。
要查看集群信息,可通過system.clusters表。
寫入數據:
向集群寫數據的方法有兩種:
一,自已指定要將哪些數據寫入哪些服務器,并直接在每個分片上執(zhí)行寫入。這是最靈活的解決方案 – 你可以使用任何分片方案,對于復雜業(yè)務特性的需求,這可能是非常重要的。這也是最佳解決方案,因為數據可以完全獨立地寫入不同的分片。
二,在分布式表上執(zhí)行 INSERT。(噠咩,不推薦)在這種情況下,分布式表會跨服務器分發(fā)插入數據。為了寫入分布式表,必須要配置分片鍵(最后一個參數)。當然,如果只有一個分片,則寫操作在沒有分片鍵的情況下也能工作,因為這種情況下分片鍵沒有意義。
數據是異步寫入的。對于分布式表的 INSERT,數據塊只寫本地文件系統(tǒng)。之后會盡快地在后臺發(fā)送到遠程服務器。
如果在 INSERT 到分布式表時服務器節(jié)點丟失或重啟(如,設備故障),則插入的數據可能會丟失。如果在表目錄中檢測到損壞的數據分片,則會將其轉移到broken子目錄,并不再使用。
關于分片:
分片可在配置文件中定義‘internal_replication’參數。
此參數設置為true時,寫操作只選一個正常的副本寫入數據。如果分布式表的子表是復制表(*ReplicaMergeTree),請使用此方案。換句話說,這其實是把數據的復制工作交給實際需要寫入數據的表本身而不是分布式表。
若此參數設置為false(默認值),寫操作會將數據寫入所有副本。實質上,這意味著要分布式表本身來復制數據。這種方式不如使用復制表的好,因為不會檢查副本的一致性,并且隨著時間的推移,副本數據可能會有些不一樣。
選擇將一行數據發(fā)送到哪個分片的方法是,首先計算分片表達式,然后將這個計算結果除以所有分片的權重總和得到余數。該行會發(fā)送到那個包含該余數的從’prev_weight’到’prev_weights weight’的前閉后開區(qū)間對應的分片上,其中 ‘prev_weights’ 是該分片前面的所有分片的權重和,‘weight’ 是該分片的權重。
分片表達式可以是由常量和表列組成的任何返回整數表達式。
下面的情況,需要關注分片方案:
- 使用需要特定鍵連接數據( IN 或 JOIN )的查詢。如果數據是用該鍵進行分片,則應使用本地 IN 或 JOIN 而不是 GLOBAL IN 或 GLOBAL JOIN,這樣效率更高。
- 使用大量服務器,但有大量小查詢,為了使小查詢不影響整個集群,讓單個客戶的數據處于單個分片上是有意義的。或者你可以配置兩級分片:將整個集群劃分為層,一個層可以包含多個分片。單個客戶的數據位于單個層上,根據需要將分片添加到層中,層中的數據隨機分布。然后給每層創(chuàng)建分布式表,再創(chuàng)建一個全局的分布式表用于全局的查詢。
Dictionary
可以將字典數據展示為一個ClickHouse的表。需要在XML配置文件中定義字典。官網文檔語焉不詳,更多介紹可見https://blog.csdn.net/vkingnew/article/details/106973674。
(不太好用的亞子)
Merge
本身不存儲數據,但可用于同時從任意多個其他的表中讀取數據。 讀是自動并行的,不支持寫入。讀取時,那些被真正讀取到數據的表的索引(如果有的話)會被使用。
創(chuàng)建語法:
如果tables_regexp命中了Merge 表本身,也不會真正引入,以免循環(huán)引用,但創(chuàng)建兩個表遞歸讀取對方數據是可行的。
Merge引擎的一個典型應用是可以像使用一張表一樣使用大量的TinyLog表。
Executable和ExecutablePool
這兩個引擎用于關聯腳本和具體表,表中的數據將由執(zhí)行腳本后生成。腳本被放在”users_scripts”目錄下。創(chuàng)建表時不會立即調用腳本,腳本將在表被查詢時調用。
剛開始感覺這個引擎沒什么用,為什么我不直接單獨跑腳本把數據收集好之后再將它們插入表呢?轉念想到腳本代碼倉庫里的幾百個(無名)腳本及對應的(無名)表,瞬間感覺這功能還怪有用的。(查找表對應的生成腳本)
(當然,我沒用過,等你去用)
應用及可能的坑點
應用
ClickHouse典型應用場景主要包括以下幾個方面:
- 大數據存儲和分析:ClickHouse能夠高效地存儲和處理海量數據,支持PB級別的數據存儲和分析,可以快速地處理大規(guī)模數據分析和數據挖掘任務。
- 實時數據分析和查詢:ClickHouse支持實時查詢和分析,具有高速的數據讀取和計算能力,可以在秒級別內返回查詢結果,適用于需要快速響應數據查詢和分析的業(yè)務場景。
- 日志處理和分析:ClickHouse能夠高效地處理日志數據,支持實時的日志分析和查詢,可以幫助企業(yè)快速地發(fā)現和解決問題。
- 業(yè)務智能分析:ClickHouse支持復雜的數據分析和計算,可以進行高級的數據挖掘和機器學習算法,幫助企業(yè)進行業(yè)務智能分析和決策。
總的來說,ClickHouse適用于需要處理大規(guī)模數據和實時查詢的業(yè)務場景,例如數據報表、日志分析、業(yè)務智能分析、廣告平臺等。
其他要說的
part與partition:
這兩個概念,我覺得是ClickHouse文檔中容易搞混的一點,特別中文文檔中出現的謎之概念『片段、片塊、部分、部件、分片』,如果不是原先就對ClickHouse有較深刻的認識,可能一時反應不過來具體指代的是什么。關于這兩者的區(qū)別,在這個鏈接及頁面內的鏈接中有較好的闡述。
關注ClickHouse版本:
ClickHouse的官方中文文檔相對英文文檔,內容要稍微落后些(你說跟俄文比如何?阿巴阿巴)。比如中文文檔中說ClickHouse不支持窗口函數,但英文文檔中表示已經支持;中文文檔中沒有projection的介紹;中文文檔中表示ClickHouse使用ZooKeeper維護元數據,然而在英文文檔中表示使用ClickHouse Keeper維護元數據;等等等等。同樣的,你的生產環(huán)境的ClickHouse版本也許與ClickHouse最新版有不小差距,所以在你考慮使用某個功能時,記得先看下當前版本是否已經支持。
關于ZooKeeper:
如上所述,ZooKeeper是ClickHouse常見版本的信息協(xié)調者。然而實際上一些行為日志也會存在其上,表的一些schema信息也會在上面做校驗。而on cluster等操作也是依賴此實現的,在數據量較大時可能會有一些意外的阻塞情況發(fā)生,所以不要太依賴ClickHouse的on cluster等會依賴ZooKeeper的操作,能拿到具體節(jié)點的情況下,到每個節(jié)點上單獨執(zhí)行是更穩(wěn)妥的。作為國內ClickHouse的布道者,宇宙條已經替大家踩過相關的坑了(當然我們團隊也踩了一次)。
此外ClickHouse本身引擎對子查詢的SQL優(yōu)化效率不高,應盡量避免復雜的子查詢語句。否則這些”cool cooler coolest”的SQL,在集群負載壓力逐漸上來之后,可能會變成半夜里響個不停的業(yè)務告警通知。