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