云數據庫ClickHouse支持的表引擎分為MergeTree、Log、Integrations和Special四個系列。本文主要對這四類表引擎進行概要介紹,并通過示例介紹常用表引擎的功能。
概述
表引擎即表的類型,在云數據庫ClickHouse中決定了如何存儲和讀取數據、是否支持索引、是否支持主備復制等。云數據庫ClickHouse支持的表引擎,請參見下表。
系列 | 描述 | 表引擎 | 特點 |
MergeTree | MergeTree系列引擎適用于高負載任務,支持大數據量的快速寫入并進行后續的數據處理,通用程度高且功能強大。 該系列引擎的共同特點是支持數據副本、分區、數據采樣等特性。 | 用于插入極大量的數據到一張表中,數據以數據片段的形式一個接著一個的快速寫入,數據片段按照一定的規則進行合并。 | |
用于將數據從一個節點復制到其他節點,并保證數據的一致性。 | |||
用于自定義數據的分區,根據您的需求定義分區鍵,以將數據分布到不同的分區中。 | |||
用于解決MergeTree表引擎相同主鍵無法去重的問題,可以刪除主鍵值相同的重復項。 | |||
在建表語句中新增標記列
| |||
在建表語句中新增 | |||
用于對主鍵列進行預先聚合,將所有相同主鍵的行合并為一行,從而大幅度降低存儲空間占用,提升聚合計算性能。 | |||
預先聚合引擎的一種,用于提升聚合計算的性能,可以指定各種聚合函數。 | |||
用于存儲Graphite數據并進行匯總,可以減少存儲空間,提高Graphite數據的查詢效率。 | |||
用于近似最近鄰搜索的索引引擎,在大規模數據集中高效地查找最接近給定查詢點的數據點。 | |||
使用倒排索引進行全文搜索,用于在大規模文本數據中進行全文搜索和檢索。 | |||
Log | Log系列引擎適用于快速寫入小表(1百萬行左右的表)并讀取全部數據的場景。 該系列引擎的共同特點如下。
| 不支持并發讀取數據文件,格式簡單,查詢性能較差,適用于暫存中間數據。 | |
支持并發讀取數據文件,將所有列存儲在同一個大文件中,減少了文件數,查詢性能比TinyLog好。 | |||
支持并發讀取數據文件,每個列會單獨存儲在一個獨立文件中,查詢性能比TinyLog好。 | |||
Integrations | Integrations系列引擎適用于將外部數據導入到云數據庫ClickHouse中,或者在云數據庫ClickHouse中直接使用外部數據源。 | Kafka | 將Kafka Topic中的數據直接導入到云數據庫ClickHouse。 |
MySQL | 將MySQL作為存儲引擎,直接在云數據庫ClickHouse中對MySQL表進行 | ||
JDBC | 通過指定JDBC連接串讀取數據源。 | ||
ODBC | 通過指定ODBC連接串讀取數據源。 | ||
HDFS | 直接讀取HDFS上特定格式的數據文件。 | ||
Special | Special系列引擎適用于特定的功能場景。 | Distributed | 本身不存儲數據,可以在多個服務器上進行分布式查詢。 |
MaterializedView | 用于創建物化視圖。 | ||
Dictionary | 將字典數據展示為一個云數據庫ClickHouse表。 | ||
Merge | 本身不存儲數據,可以同時從任意多個其他表中讀取數據。 | ||
File | 直接將本地文件作為數據存儲。 | ||
NULL | 寫入數據被丟棄,讀取數據為空。 | ||
Set | 數據總是保存在RAM中。 | ||
Join | 數據總是保存在內存中。 | ||
URL | 用于管理遠程HTTP、HTTPS服務器上的數據。 | ||
View | 本身不存儲數據,僅存儲指定的 | ||
Memory | 數據存儲在內存中,重啟后會導致數據丟失。查詢性能極好,適合于對于數據持久性沒有要求的1億以下的小表。在云數據庫ClickHouse中,通常用來做臨時表。 | ||
Buffer | 為目標表設置一個內存Buffer,當Buffer達到了一定條件之后會寫入到磁盤。 |
表引擎的更多信息,具體請參見表引擎介紹。
MergeTree
MergeTree表引擎主要用于海量數據分析,支持數據分區、存儲有序、主鍵索引、稀疏索引和數據TTL等。MergeTree表引擎支持云數據庫ClickHouse的所有SQL語法,但是部分功能與標準SQL存在差異。
本文以主鍵為例進行介紹。云數據庫ClickHouse的SQL語法中主鍵用于去重,保持數據唯一,而在MergeTree表引擎中,其主要作用是加速查詢,即便在Compaction完成后,主鍵相同的數據行也仍舊共同存在。
MergeTree表引擎的更多信息,具體請參見MergeTree。
示例如下。
創建表test_tbl,主鍵為
id
和create_time
,并且按照主鍵進行存儲排序,按照create_time
進行數據分區。CREATE TABLE test_tbl ON CLUSTER default ( id UInt16, create_time Date, comment Nullable(String) ) ENGINE = MergeTree() PARTITION BY create_time ORDER BY (id, create_time) PRIMARY KEY (id, create_time) SETTINGS index_granularity=8192;
寫入主鍵重復的數據。
insert into test_tbl values(1, '2019-12-13', null); insert into test_tbl values(1, '2019-12-13', null); insert into test_tbl values(2, '2019-12-14', null); insert into test_tbl values(3, '2019-12-15', null); insert into test_tbl values(3, '2019-12-15', null);
查詢數據。
select * from test_tbl;
查詢結果如下。
┌─id─┬─create_time─┬─comment──┐ │ 1 │ 2019-12-13 │ NULL │ │ 1 │ 2019-12-13 │ NULL │ │ 2 │ 2019-12-14 │ NULL │ │ 3 │ 2019-12-15 │ NULL │ │ 3 │ 2019-12-15 │ NULL │ └────┴─────────────┴──────────┘
由于MergeTree系列表引擎采用類似LSM Tree的結構,很多存儲層處理邏輯直到Compaction期間才會發生,因此需執行optimize語句強制后臺Compaction。
optimize table test_tbl final;
再次查詢數據。
select * from test_tbl;
查詢結果如下,主鍵重復的數據仍存在。
┌─id─┬─create_time─┬─comment──┐ │ 1 │ 2019-12-13 │ NULL │ │ 1 │ 2019-12-13 │ NULL │ │ 2 │ 2019-12-14 │ NULL │ │ 3 │ 2019-12-15 │ NULL │ │ 3 │ 2019-12-15 │ NULL │ └────┴─────────────┴──────────┘
ReplacingMergeTree
為了解決MergeTree表引擎相同主鍵無法去重的問題,云數據庫ClickHouse提供了ReplacingMergeTree表引擎,用于刪除主鍵值相同的重復項。
雖然ReplacingMergeTree表引擎提供了主鍵去重的能力,但是仍然存在很多限制,因此ReplacingMergeTree表引擎更多被用于確保數據最終被去重,而無法保證查詢過程中主鍵不重復,主要限制如下。
在分布式場景下,相同主鍵的數據可能被分布到不同節點上,不同分片間可能無法去重。
在沒有徹底optimize之前,可能無法達到主鍵去重的效果,比如部分數據已經被去重,而另外一部分數據仍舊有主鍵重復。
optimize是后臺動作,無法預測具體執行時間點。
手動執行optimize在海量數據場景下需要消耗大量時間,無法滿足業務即時查詢的需求。
ReplacingMergeTree表引擎的更多信息,具體請參見ReplacingMergeTree。
示例如下。
創建表test_tbl_replacing。
CREATE TABLE test_tbl_replacing ( id UInt16, create_time Date, comment Nullable(String) ) ENGINE = ReplacingMergeTree() PARTITION BY create_time ORDER BY (id, create_time) PRIMARY KEY (id, create_time) SETTINGS index_granularity=8192;
寫入主鍵重復的數據。
insert into test_tbl_replacing values(1, '2019-12-13', null); insert into test_tbl_replacing values(1, '2019-12-13', null); insert into test_tbl_replacing values(2, '2019-12-14', null); insert into test_tbl_replacing values(3, '2019-12-15', null); insert into test_tbl_replacing values(3, '2019-12-15', null);
查詢數據。
select * from test_tbl_replacing;
查詢結果如下。
┌─id─┬─create_time─┬─comment──┐ │ 1 │ 2019-12-13 │ NULL │ │ 1 │ 2019-12-13 │ NULL │ │ 2 │ 2019-12-14 │ NULL │ │ 3 │ 2019-12-15 │ NULL │ │ 3 │ 2019-12-15 │ NULL │ └────┴─────────────┴──────────┘
由于MergeTree系列表引擎采用類似LSM Tree的結構,很多存儲層處理邏輯直到Compaction期間才會發生,因此需執行optimize語句強制后臺Compaction。
optimize table test_tbl_replacing final;
再次查詢數據。
select * from test_tbl_replacing;
查詢結果如下,主鍵重復的數據已消除。
┌─id─┬─create_time─┬─comment──┐ │ 1 │ 2019-12-13 │ NULL │ │ 2 │ 2019-12-14 │ NULL │ │ 3 │ 2019-12-15 │ NULL │ └────┴─────────────┴──────────┘
CollapsingMergeTree
CollapsingMergeTree表引擎用于消除ReplacingMergeTree表引擎的功能限制。該表引擎要求在建表語句中指定一個標記列Sign,按照Sign的值將行分為兩類:Sign=1
的行稱為狀態行,用于新增狀態。Sign=-1
的行稱為取消行,用于刪除狀態。
CollapsingMergeTree表引擎雖然解決了主鍵相同數據即時刪除的問題,但是狀態持續變化且多線程并行寫入情況下,狀態行與取消行位置可能亂序,導致無法正常折疊(刪除)。
后臺Compaction時會將主鍵相同、Sign
相反的行進行折疊(刪除),而尚未進行Compaction的數據,狀態行與取消行同時存在。因此為了能夠達到主鍵折疊(刪除)的目的,需要業務層進行如下操作。
記錄原始狀態行的值,或者在執行刪除狀態操作前先查詢數據庫獲取原始狀態行的值。
具體原因:執行刪除狀態操作時需要寫入取消行,而取消行中需要包含與原始狀態行主鍵一樣的數據(Sign列除外)。
在進行
count()
、sum(col)
等聚合計算時,可能會存在數據冗余的情況。為了獲得正確結果,業務層需要改寫SQL,將count()
、sum(col)
分別改寫為sum(Sign)
、sum(col * Sign)
。具體原因如下。
后臺Compaction時機無法預測,在發起查詢時,狀態行和取消行可能尚未進行折疊(刪除)。
云數據庫ClickHouse無法保證主鍵相同的行落在同一個節點上,不在同一節點上的數據無法進行折疊(刪除)。
CollapsingMergeTree表引擎的更多信息,具體請參見CollapsingMergeTree。
示例如下。
創建表test_tbl_collapsing。
CREATE TABLE test_tbl_collapsing ( UserID UInt64, PageViews UInt8, Duration UInt8, Sign Int8 ) ENGINE = CollapsingMergeTree(Sign) ORDER BY UserID;
插入狀態行
Sign=1
。INSERT INTO test_tbl_collapsing VALUES (4324182021466249494, 5, 146, 1);
說明如果先插入取消行,再插入狀態行,可能會導致位置亂序,即使強制后臺Compaction,也無法進行主鍵折疊(刪除)。
插入取消行
Sign=-1
,除Sign
列外其他值與插入的狀態行一致。同時,插入一行相同主鍵數據的新狀態行。INSERT INTO test_tbl_collapsing VALUES (4324182021466249494, 5, 146, -1), (4324182021466249494, 6, 185, 1);
查詢數據。
SELECT * FROM test_tbl_collapsing;
查詢結果如下。
┌────────UserID───────┬─PageViews─┬─Duration─┬─Sign──┐ │ 4324182021466249494 │ 5 │ 146 │ 1 │ │ 4324182021466249494 │ 5 │ 146 │ -1 │ │ 4324182021466249494 │ 6 │ 185 │ 1 │ └─────────────────────┴───────────┴──────────┴───────┘
如果您需要對指定列進行聚合計算,以
sum(col)
為例,為了獲得正確結果,需改寫SQL語句如下。SELECT UserID, sum(PageViews * Sign) AS PageViews, sum(Duration * Sign) AS Duration FROM test_tbl_collapsing GROUP BY UserID HAVING sum(Sign) > 0;
進行聚合計算后,查詢結果如下。
┌────────UserID───────┬─PageViews─┬─Duration──┐ │ 4324182021466249494 │ 6 │ 185 │ └─────────────────────┴───────────┴───────────┘
由于MergeTree系列表引擎采用類似LSM Tree的結構,很多存儲層處理邏輯直到Compaction期間才會發生,因此需執行optimize語句強制后臺Compaction。
optimize table test_tbl_collapsing final;
再次查詢數據。
SELECT * FROM test_tbl_collapsing;
查詢結果如下。
┌────────UserID───────┬─PageViews─┬─Duration─┬─Sign──┐ │ 4324182021466249494 │ 6 │ 185 │ 1 │ └─────────────────────┴───────────┴──────────┴───────┘
VersionedCollapsingMergeTree
為了解決CollapsingMergeTree表引擎亂序寫入導致無法正常折疊(刪除)問題,云數據庫ClickHouse提供了VersionedCollapsingMergeTree表引擎,在建表語句中新增一列Version
,用于在亂序情況下記錄狀態行與取消行的對應關系。后臺Compaction時會將主鍵相同、Version
相同、Sign
相反的行折疊(刪除)。
與CollapsingMergeTree表引擎類似,在進行count()
、sum(col)
等聚合計算時,業務層需要改寫SQL,將count()
、sum(col)
分別改寫為sum(Sign)
、sum(col * Sign)
。
VersionedCollapsingMergeTree表引擎的更多信息,具體請參見VersionedCollapsingMergeTree。
示例如下。
創建表test_tbl_Versioned。
CREATE TABLE test_tbl_Versioned ( UserID UInt64, PageViews UInt8, Duration UInt8, Sign Int8, Version UInt8 ) ENGINE = VersionedCollapsingMergeTree(Sign, Version) ORDER BY UserID;
插入取消行
Sign=-1
。INSERT INTO test_tbl_Versioned VALUES (4324182021466249494, 5, 146, -1, 1);
插入狀態行
Sign=1
、Version=1
,其他列值與插入的取消行一致。同時,插入一行相同主鍵數據的新狀態行。INSERT INTO test_tbl_Versioned VALUES (4324182021466249494, 5, 146, 1, 1),(4324182021466249494, 6, 185, 1, 2);
查詢數據。
SELECT * FROM test_tbl_Versioned;
查詢結果如下。
┌────────UserID───────┬─PageViews─┬─Duration─┬─Sign───┬Version─┐ │ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 │ │ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 │ │ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │ └─────────────────────┴───────────┴──────────┴────────┴────────┘
如果您需要對指定列進行聚合計算,以
sum(col)
為例,為了獲得正確結果,需改寫SQL語句如下。SELECT UserID, sum(PageViews * Sign) AS PageViews, sum(Duration * Sign) AS Duration FROM test_tbl_Versioned GROUP BY UserID HAVING sum(Sign) > 0;
進行聚合計算后,查詢結果如下。
┌────────UserID───────┬─PageViews─┬─Duration─┐ │ 4324182021466249494 │ 6 │ 185 │ └─────────────────────┴───────────┴──────────┘
由于MergeTree系列表引擎采用類似LSM Tree的結構,很多存儲層處理邏輯直到Compaction期間才會發生,因此需執行optimize語句強制后臺Compaction。
optimize table test_tbl_Versioned final;
再次查詢數據。
SELECT * FROM test_tbl_Versioned;
查詢結果如下。
┌────────UserID───────┬─PageViews─┬─Duration─┬─Sign───┬Version─┐ │ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │ └─────────────────────┴───────────┴──────────┴────────┴────────┘
SummingMergeTree
SummingMergeTree表引擎用于對主鍵列進行預先聚合,將所有相同主鍵的行合并為一行,從而大幅度降低存儲空間占用,提升聚合計算性能。
使用SummingMergeTree表引擎時,需要注意如下幾點。
云數據庫ClickHouse只在后臺Compaction時才會對主鍵列進行預先聚合,而Compaction的執行時機無法預測,所以可能存在部分數據已經被預先聚合、部分數據尚未被聚合的情況。因此,在執行聚合計算時,仍需要使用
GROUP BY
子句。在預先聚合時,云數據庫ClickHouse會對主鍵列之外的其他所有列進行預聚合。如果這些列是可聚合的(比如數值類型),則直接sum求和。如果不可聚合(比如String類型),則會隨機選擇一個值。
通常建議將SummingMergeTree表引擎與MergeTree表引擎組合使用,MergeTree表引擎存儲完整的數據,SummingMergeTree表引擎用于存儲預先聚合的結果。
SummingMergeTree表引擎的更多信息,具體請參見SummingMergeTree。
示例如下。
創建表test_tbl_summing。
CREATE TABLE test_tbl_summing ( key UInt32, value UInt32 ) ENGINE = SummingMergeTree() ORDER BY key;
寫入數據。
INSERT INTO test_tbl_summing Values(1,1),(1,2),(2,1);
查詢數據。
select * from test_tbl_summing;
查詢結果如下。
┌─key─┬value─┐ │ 1 │ 1 │ │ 1 │ 2 │ │ 2 │ 1 │ └─────┴──────┘
由于MergeTree系列表引擎采用類似LSM Tree的結構,很多存儲層處理邏輯直到Compaction期間才會發生,因此需執行optimize語句強制后臺Compaction。
optimize table test_tbl_summing final;
強制后臺Compaction后,仍舊需要執行
GROUP BY
子句進行聚合計算,再次查詢數據。SELECT key, sum(value) FROM test_tbl_summing GROUP BY key;
查詢結果如下,主鍵重復的數據已進行了聚合。
┌─key─┬value─┐ │ 1 │ 3 │ │ 2 │ 1 │ └─────┴──────┘
AggregatingMergeTree
AggregatingMergeTree表引擎也是預先聚合引擎的一種,用于提升聚合計算的性能。與SummingMergeTree表引擎的區別在于,SummingMergeTree表引擎用于對非主鍵列進行sum聚合,而AggregatingMergeTree表引擎可以指定各種聚合函數。
AggregatingMergeTree的語法比較復雜,需要結合物化視圖或云數據庫ClickHouse的特殊數據類型AggregateFunction一起使用。
AggregatingMergeTree表引擎的更多信息,具體請參見AggregatingMergeTree。
示例如下。
結合物化視圖使用
創建明細表visits。
CREATE TABLE visits ( UserID UInt64, CounterID UInt8, StartDate Date, Sign Int8 ) ENGINE = CollapsingMergeTree(Sign) ORDER BY UserID;
對明細表visits建立物化視圖visits_agg_view,并使用
sumState
和uniqState
函數對明細表進行預先聚合。CREATE MATERIALIZED VIEW visits_agg_view ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(StartDate) ORDER BY (CounterID, StartDate) AS SELECT CounterID, StartDate, sumState(Sign) AS Visits, uniqState(UserID) AS Users FROM visits GROUP BY CounterID, StartDate;
寫入數據至明細表visits中。
INSERT INTO visits VALUES(0, 0, '2019-11-11', 1); INSERT INTO visits VALUES(1, 1, '2019-11-12', 1);
使用聚合函數
sumMerge
和uniqMerge
對物化視圖進行聚合,并查詢聚合數據。SELECT StartDate, sumMerge(Visits) AS Visits, uniqMerge(Users) AS Users FROM visits_agg_view GROUP BY StartDate ORDER BY StartDate
說明函數
sum
和uniq
不能再使用,否則會出現SQL報錯:Illegal type AggregateFunction(sum, Int8) of argument for aggregate function sum...查詢結果如下。
┌──StartDate──┬─Visits─┬─Users──┐ │ 2019-11-11 │ 1 │ 1 │ │ 2019-11-12 │ 1 │ 1 │ └─────────────┴────────┴────────┘
結合特殊數據類型AggregateFunction使用
創建明細表detail_table。
CREATE TABLE detail_table ( CounterID UInt8, StartDate Date, UserID UInt64 ) ENGINE = MergeTree() PARTITION BY toYYYYMM(StartDate) ORDER BY (CounterID, StartDate);
寫入數據至明細表detail_table中。
INSERT INTO detail_table VALUES(0, '2019-11-11', 1); INSERT INTO detail_table VALUES(1, '2019-11-12', 1);
創建聚合表agg_table,其中
UserID
列的類型為AggregateFunction。CREATE TABLE agg_table ( CounterID UInt8, StartDate Date, UserID AggregateFunction(uniq, UInt64) ) ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(StartDate) ORDER BY (CounterID, StartDate);
使用聚合函數
uniqState
將明細表的數據插入至聚合表中。INSERT INTO agg_table select CounterID, StartDate, uniqState(UserID) from detail_table group by CounterID, StartDate;
說明不能使用
INSERT INTO agg_table VALUES(1, '2019-11-12', 1);
語句向聚合表插入數據,否則會出現SQL報錯:Cannot convert UInt64 to AggregateFunction(uniq, UInt64)...使用聚合函數
uniqMerge
對聚合表進行聚合,并查詢聚合數據。SELECT uniqMerge(UserID) AS state FROM agg_table GROUP BY CounterID, StartDate;
查詢結果如下。
┌─state─┐ │ 1 │ │ 1 │ └───────┘