
文章目錄
ClickHouse的索引深入了解
一、一級索引
二、二級索引(跳數索引)
ClickHouse的索引深入了解
一、一級索引
在MergeTree中PRIMARY KEY 主鍵并不用于去重,而是用于索引,加快查詢速度,MergeTree會根據index_granularity間隔(預設8192行),為資料表生成一級索引并儲存至primary.idx檔案内,索引資料按照PRIMARY KEY 排序,相對于使用PRIMARY KEY 更常見的方式是通過ORDER BY 方式指定主鍵。
- 稀疏索引
primary.idx檔案内的一級索引采用稀疏索引實作。有稀疏索引就有稠密索引,二者差別如下:
在稠密索引中每一行索引标記都會對應到一行具體的資料記錄。而在稀疏索引中每一行索引标記對應的是一段資料,而不是一行。
稀疏索引的優勢顯而易見,僅需要使用少量的索引标記就能夠記錄大量的資料區間位置資訊,而且資料量越大優勢越明顯。在MergeTree系列引擎表中對應的primary.idx檔案就是稀疏索引,由于稀疏索引占用空間小,是以primary.idx内的索引資料常駐記憶體。
- 索引粒度
在ClickHouse MergeTree引擎中預設的索引粒度是8192,參數為index_granularity,一般我們不會修改此值,按照預設8192即可。我們可以通過以下sql語句檢視每個MergeTree引擎表對應的index_granulariry的值:
node1 :) show create table t_mt;
索引粒度對于MergeTree表引擎非常重要,可以根據整個資料的長度,按照索引粒度對資料進行标注,然後抽取對應的資料形成索引。
- 索引形成過程
表資料以index_granularity的粒度(預設8192)被标記成多個小區間,其中每個區間最多8192行資料,每個區間标記後形成一個MarkRange,通過start和end表示MarkRange的具體範圍,資料檔案也會按照index_granularity的間隔粒度生成壓縮資料塊。由于是稀疏索引,MergeTree需要間隔index_granularity行資料生成一條索引,同時對應一個索引編号,每個MarRange與一個索引編号對應,通過與start及end對應的索引編号的取值,可以得到對應的數值區間;索引編号對應的索引值會依據聲明的主鍵字段擷取,最終索引編号和索引值被寫入primary.idx檔案中儲存。
假設現在有一份測試資料,共192行記錄,其中主鍵ID為String類型,ID值從A000開始,後面依次為A001、A002...直到A192為止,假設我們設定MergeTree的索引粒度index_granularity=3,根據索引的生成規則,primary.idx檔案内的索引資料如下:
根據索引資料,MergeTree将此資料片段劃分成192/3=64個小的MarkRange,其中所有MarkRange的最大數值區間為[A000,+inf),劃分的MarkRange如下:
- 索引查詢過程
使用索引查詢其實就是兩個數值區間的交集判斷,其中一個區間是有基于主鍵的查詢條件轉換而來的條件區間,而另一個區間是上圖中MarkRange對應的數值區間。
整個索引查詢的過程大緻分為3個步驟:
1、生成查詢條件區間
查詢時首先将查詢條件轉換為條件區間,即便是單個值的查詢條件也會轉換成區間的形式,例如:
WHERE ID='A003'
['A003','A003']
WHERE ID>'A000'
['A000',+inf]
WHERE ID<'A188'
(-inf,'A188']
WHERE ID like 'A006%'
('A006','A007']
2、遞歸交集判斷
以遞歸的方式依次對MarkRange的數值區間與條件區間做交集判斷,從最大的區間[A000,+inf)開:
- 如果不存在交集,則直接忽略掉整段MarkRange
- 如果存在交集,且MarkRange步長大于8(end-start),則将此區間進一步拆分成8個區間(由merge_tree_coarse_index_granularity指定,預設值為8),并重複此規則,繼續做遞歸交集判斷。
- 如果存在交集,且MarkRange不可再分解(步長小于8),則記錄MarkRange并傳回。
3、合并MarkRange區間
将最終比對的MarkRange聚在一起,合并他們的範圍。
當查詢條件WHERE ID ='A003'的時候,最終讀取[A000,A003)和[A003,A006]兩個區間的資料即可,他們對應的MarkRange(start:0,end:2)範圍,而無其他無用的區間都被裁剪過濾掉,因為MarkRange轉換的數值區間是閉區間,是以會額外比對到臨近的一個區間,完整的邏輯圖如下圖所示:
二、二級索引(跳數索引)
除了一級索引之外,MergeTree同樣支援二級索引,二級索引又稱為跳數索引,由資料的聚合資訊建構而成,根據索引類型的不同,其聚合資訊的内容也不同,跳數索引的目的與一級索引一樣,也是幫助查詢時減少資料掃描的範圍。
跳數索引需要在Create語句内定義,完整文法如下:
INDEX index_name expr TYPE index_type(...) GRANULARITY granularity
對以上參數的解釋如下:
- index_name:定義的二級索引名稱
- index_type:跳數索引類型,最常用就是minmax索引類型。minmax索引記錄了一段資料内的最小和最大極值,其索引的作用類似分區目錄,能夠快速跳過無用的資料區間。
- granularity:定義聚合資訊彙總的粒度。
- 與一級索引一樣,如果在建表語句中聲明了跳數索引,則會在路徑“/var/lib/ClickHouse/data/DATABASE/TABLE/PARTITION/”目錄下生成索引與标記檔案(skp_idx.idx與skp_idx.mrk)。
- 在接觸跳數索引時,很容易将index_granularity與granularity概念混淆,對于跳數索引而言,index_granularity定義了資料的粒度,而granularity定義了聚合資訊彙總的粒度,也就是說,granularity定義了一行跳數索引能夠跳過多少個index_granularity區間的資料。
minmax跳數索引的生成規則
minmax跳數索引聚合資訊是在一個index_granularity區間内資料的最小和最大極值。首先,資料按照index_granularity粒度間隔将資料劃分成n段,總共有[0~n-1]個區間(n=total_rows/index_granularity,向上取整),接着根據跳數索引從0區間開始,依次按index_granularity粒度從資料中擷取聚合資訊,每次向前移動1步,聚合資訊逐漸累加,最後當移動granularity次區間時,則彙總并生成一行跳數索引資料。
以下圖為例:假設index_granularity=8192且granularity=3,則資料會按照index_granularity劃分成n等份,MergeTree從第0段分區開始,依次擷取聚合資訊,當擷取到第3個分區時(granularity=3),則彙總并生成第一行minmax索引(前3段minmax極值彙總後取值為[1,9])。
minmax跳數索引案例:
#删除表 t_mt
node1 :) drop table t_mt;
#重新建立t_mt表,包含二級索引
node1 :)CREATE TABLE t_mt
(
id UInt8,
name String,
age UInt8,
birthday Date,
location String,
INDEX a id TYPE minmax GRANULARITY 5
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(birthday)
ORDER BY (id, age)
PRIMARY KEY id
#插入資料
insert into t_mt values (1,'張三',18,'2021-06-01','上海'), (2,'李四',19,'2021-02-10','北京'), (3,'王五',12,'2021-06-01','天津'), (1,'馬六',10,'2021-06-18','上海'), (5,'田七',22,'2021-02-09','廣州');
#檢視資料分區路徑
- 📢本文由 Lansonli 原創
- 📢停下休息的時候不要忘了别人還在奔跑,希望大家抓緊時間學習,全力奔赴更美好的生活✨