ClickHouse及其MergeTree引擎

ClickHouse是一個用於聯機分析(OLAP)的列式數據庫管理系統(DBMS)。git

OLAP場景的關鍵特徵

OLAP(OnLine Analysis Processing ,聯機分析處理),核心思想就是創建多維度的數據立方體,以維度(Dimension)和度量(Measure)爲基本概念,輔以元數據,實現能夠鑽取、切片、切塊、旋轉等靈活、系統、直觀的數據展示。github

它的特徵有:算法

  • 絕大多數是讀請求

-數據以至關大的批次(> 1000行)更新,而不是單行更新;或者根本沒有更新。sql

  • 已添加到數據庫的數據不能修改。
  • 對於讀取,從數據庫中提取至關多的行,但只提取列的一小部分。
  • 寬表,即每一個表包含着大量的列
  • 查詢相對較少(一般每臺服務器每秒查詢數百次或更少)
  • 對於簡單查詢,容許延遲大約50毫秒
  • 列中的數據相對較小:數字和短字符串(例如,每一個URL 60個字節)
  • 處理單個查詢時須要高吞吐量(每臺服務器每秒可達數十億行)
  • 事務不是必須的
  • 對數據一致性要求低
  • 每一個查詢有一個大表。除了他之外,其餘的都很小。
  • 查詢結果明顯小於源數據。換句話說,數據通過過濾或聚合,所以結果適合於單個服務器的RAM中

ClickHouse不適用的場景

  • 不支持事務
  • 不擅長按照主鍵進行粒度的查詢(雖然支持)
  • 不擅長按行刪除數據(雖然支持)

ClickHouse的MergeTree引擎

MergeTree是ClickHouse最經常使用的表引擎,支持主鍵索引,數據分區,數據副本和數據採樣等功能。數據庫

MergeTree的建立方式和存儲方式

MergeTree寫入一批數據時,數據會以數據片斷的的形式寫入磁盤,而且數據片斷不可修改。爲了逼迫數據片斷過多,ClickHouse會經過後臺線程,按期合併這些數據片斷。緩存

MergeTree的建立方式

CREATE TABLE [IF NOT EXISTS] [db_name].table_name (
  
)ENGINE = MergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS name=value,...]
複製代碼

(1)PARTITION BY[選填]: 分區鍵,用於指定表數據以何種形式進行分區。分區鍵支持:單個列字段,元組的形式使用多個列字段,列表達式。不聲明的話,ClickHouse會生成一個all的分區。服務器

(2)ORDER BY[必填]: 排序鍵,指定在一個數據片斷內,數據以何種標準排序。默認狀況下主鍵和排序鍵相同。markdown

(3)PRIMARY KEY[選填]: 主鍵,主鍵字段生成一級索引,用於加上表查詢。MergeTree運行重複數據(ReplacingMergeTree能夠去重)多線程

(4) SAMPLE BY [選填]: 抽樣表達式,聲明數據的採樣標準。聲明瞭此配置項,主鍵的配置也有聲明一樣的表達式。函數

ENGINE = MERGETREE()
ORDER BY (CounterID, EventDate, intHASH32(UserID))
SAMPLE BY intHASH32(UserID)
複製代碼

(5) SETTINGS: index_granularity表示索引的粒度,默認是8192。 MergeTree在默認狀況下,每隔8192行數據才生產一條索引。

MergeTree的存儲結構

mergetree_structure

(1) partiton: 分區目錄,數據以分區目錄的形式存放。

(2) columns.txt:列信息文件

(3) count.txt: 計數文件,記錄當前數據目錄下數據的總行數。

(4) primay.idx: 一級索引文件,是稀疏索引,以二進制形式存儲。

(5) [Column].bin: 數據文件,存儲某一列的數據,壓縮格式存儲,默認是LZ4壓縮格式。每一個列字段擁有獨立.bin文件。

(6) [Column].mrk: 列字段標記文件,保存.bin中數據的偏移量信息。ClickHouse經過primay.idx稀疏索引,找到對應數據的偏移量信息(.mrk),而後經過偏移量直接從.bin文件中讀取數據。

(7) [Column].mrk2: 使用了自適應大小的索引間隔,標記文件會以.mrk2命名。原理和.mrk相同。

(8) partition.datminmax_[Column].idx:使用了分區鍵就會生成。partiton.dat記錄當前分區下,分區表達式最終生成的值。minmax索引記錄當前分區下分區字段對應原始數據的最大值和最小值。這樣方便查詢時,跳過沒必要要數據分區,減小須要掃描的範圍。

數據分區

MergeTree的分區由分區ID決定,分區ID由分區鍵的取值決定。

(1)不指定分區鍵:默認分區爲all,全部數據都寫入這個分區。

(2)使用整型(兼容UInt64,包括有符號整型和無符號整型):若是沒法轉化成日期類型YYYYMMDD格式,按照該整型的字符串形式取值。

(3)使用日期類型:按照日期類型YYYYMMDD格式化後字符串輸出。

(4)其餘類型:分區鍵類型不屬於整型或者日期類型,例如Float64/String, 經過128Hash算法去Hash值做爲分區ID的取值。

分區目錄的命名規則

MergeTree的特色在於分區的合併,從分區的命名能夠解讀其合併邏輯。命名規則:

PartitionID_MinBlockNum_MaxBlockNum_Level
複製代碼

完整的目錄合併過程

一級索引

MergeTree的主鍵使用PRIMARY KEY定義,按照index_granularity間隔,爲數據表生成一級索引並保存至primary.idx文件,索引數據按照PRIMARY KEY排序。相比使用PRIMARY KEY,更常見的方式是經過ORDER BY指定主鍵。

數據存儲

MergeTree中,數據是按列存儲的,每一個列字段有對應的.bin文件。按列獨立存儲能夠更好地壓縮數據,最小化數據掃描的範圍。

壓縮數據塊的邏輯

數據標記

數據標記和和索引區間是一一對齊的, 都按照index_granularity的間隔。

標記文件與.bin文件也一一對應,每個[Column].bin文件都有一個對應的[Column].mrk文件,記錄數據在.bin文件中的偏移量信息。

.mrk中每行數據,對應壓縮文件中偏移量和解壓縮文件中偏移量。

標記數據和一級索引數據不同,它不能常駐內存,使用LRU緩存策略替換。

數據寫入過程

數據讀取過程

MergeTree系列表引擎

ReplacingMergeTree

MergeTree擁有主鍵,可是主鍵沒有惟一鍵的約束。ReplacingMergeTree的處理邏輯

(1) 使用ORDER BY排序鍵做爲判斷重複數據的惟一鍵。

(2) 只有在合併分區的時候纔會觸發刪除重複數據的邏輯

(3) 以數據分區爲單位刪除重複數據。當分區合併時,同一分區的重複數據被刪除,不一樣分區的合併數據不刪除。

(4) 數據去重時,分區內的數據已經基於ORDER BY進行了排序,因此可以找到某些相鄰的重複數據。

(5) 數據去重的策略

SummingMergeTree

MergeTree的每一個數據分區,數據按照order by 表達式排序,主鍵索引按照PRIMARY KEY表達式取值並排序。通常狀況下,只須要定義ORDER BY便可。

SummingMergeTreeAggregatingMergeTree的聚合都是根據order by進行的。

若是同時聲明瞭ORDER BYPRIMARY KEYMergeTree會強制要求PRIMARY KEY字段必須是ORDER BY的前綴。

處理邏輯:

(1) order by排序鍵做爲聚合數據的條件Key

(2) 只有在合併分區的時候纔會觸發刪除重複數據的邏輯

(3) 以數據分區爲單位聚合數據。當分區合併時,同一分區的聚合key相同的數據會被合併彙總,而不一樣分區之間的數據不會被彙總。

(4) 定義引擎時,指定了columns彙總列,則sum彙總這些列字段;若是未指定,則聚合全部非主鍵的數值類型字段

(5) 彙總數據時,同一分區內,相同聚合key的多行數據合併成一行。其中,彙總字段進行sum計算,非彙總字段。使用第一行的數據。

(6) 以前嵌套結構,但列字段必需要Map後綴結尾。嵌套類型中,默認以第一個字段做爲聚合key。除第一個字段外,任何名稱以Key,Id,Type後綴結尾的字段,和第一個字段組成複合key。

沒有設置了ver版本號,保留同一組重複數據中的最後一行

設置了ver版本號,保留同一組重複數據中的ver取值最大的一行

AggregatingMergeTree

AggregatingMergeTreeorder by,``primary keySummingMergeTree`。

看下面例子

CREATE TABLE agg_table(
	id String,
  city String,
  code AggregateFunction(uniq,String),
  value AggregateFunction(sum,UInt32),
  create_time DateTime,
)ENGINE = AggregatingMergeTree
PARTITIION BY toYYYYMM(create_time)
ORDER BY (id,city)
PRIMARY KEY id
複製代碼

其中,列字段id, city 是聚合條件,等同於

GROUP BY id,city
複製代碼

code,value是聚合字段,等同於

UNIQ(code), SUM(value)
複製代碼

AggregateFunction是ClickHOuse一種特殊的數據類型,它可以以二進制的形式存儲中間狀態。寫入數據,須要調用*State函數,讀取數據,須要調用 *Merge函數

一般會使用MergeTree做爲底表,存儲全量的數據,而且新建一張物化視圖。

CREATE MATERIALIZED VIEW agg_view
ENGINE = AggregatingMergeTree()
PARTITION BY city
ORDER BY (id,city)
AS SELECT
	id,
	city,
	uniqState(code) AS code,
	sumState(value) AS value
FROM agg_table basic
GROUP BY id,city
複製代碼

CollapsingMergeTree

對於ClickHouse這類高性能分析性數據庫而言,對數據源文件修改是代價高昂的操做。相對於修改源文件,會將修改和刪除轉換成新增操做,以增代刪。

CollapsingMergeTree定義一個sign標記位字段,記錄數據行的狀態。若是sign標記爲1,是一行有效的數據。sign標記爲-1,表示這行數據須要被刪除。

CREATE TABLE collpase_table(
	id String,
  code Int32,
  create_time DateTime,
  sign Int8
)ENGINE=CollapsingMergeTree(sign)
PARTITION BY toYYMM(create_time)
ORDER BY id
複製代碼

CollapsingMergeTree 一樣以order by判斷惟一性的依據。

(1)摺疊數據不是實時觸發的,和其餘MergeTree變種引擎同樣,這項特性在分區合併以後纔會體現。分區合併以前,用戶看到的是舊的數據。有2中方法解決:

  • 查詢以前使用optimize TABLE table_name FINAL 強制分區合併,可是方法效率很低

  • 改變查詢方式

    SELECT id, sum(code), count(coude), avg(code), uniq(code)
    FROM collpase_table
    GROUP BY id
    複製代碼

    改爲

    SELECT id, sum(code*sign), count(code*sign), avg(code*sign), uniq(code*sign)
    FROM collpase_table
    GROUP BY id
    HAVING SUM(sign) >0
    複製代碼

(2)只有相同分區內的數據才能被摺疊,

(3)CollapsingMergeTree對寫入順序有着嚴格的要求。若是按照正常順序寫入,先寫入sign=1, 再寫入sign=-1,則可以正常摺疊。這種現象是CollapsingMergeTree的處理機制引發的,它要求sign=1和sign=-1的數據相鄰,而分區內的數據基於order by排序,要實現sign=1和sign=-1的數據相鄰,須要按照順序寫入。

若是數據的寫入程序是單線程,能夠控制寫入順序;若是是多線程,可使用CollapsingMergeTree

Clickhouse的SQL語句查詢

query

with rollup

rollup按照聚合鍵從右向左上卷數據,基於聚合函數依次生成分組小計和總計。加上聚合鍵個數爲n, 最終會生成小計的個數是n+1。

SELECT table,name,sum(bytes_on_disk) FROM system.parts
GROUP BY table,name
WITH ROLLUP
ORDER BY table
複製代碼

最終的返回結果,附加了顯示名稱爲空的小計彙總行。

with cube

cube像立方體同樣,居於聚合鍵之間的全部組合生成小計信息。聚合鍵的個數爲n,最終小計組合的個數是2的n次方。

SELECT database,table,name,sum(bytes_on_disk) FROM
(
  SELECT database,table,name,bytes_on_disk FROM system.parts WHERE table = 'hits_v1'
)
GROUP BY database,table,name
WITH CUBE
ORDER BY database,table,name
複製代碼

with totals

基於聚合函數對全部數據進行彙總

SELECT database,sum(bytes_on_disk),count(table) FROM system.parts
GROUP BY database
WITH TOTALS
複製代碼

參考文檔

  1. 什麼是ClickHouse
  2. ClickH原理解析和應用實踐

個人公衆號:lyp分享的地方

個人知乎專欄: zhuanlan.zhihu.com/c_127546654…

個人博客:www.liangyaopei.com

Github Page: liangyaopei.github.io/

相關文章
相關標籤/搜索