8 表的引擎
和 MySQL 有相似,ClickHouse 創建表也需要指定引擎。ClickHouse有豐富的表引擎,以適合不同的應用場景。這里介紹幾種常用的。更多信息,請參見table-engines。
8.1 MergeTree
ClickHouse 中最強大的表引擎當屬 MergeTree (合並樹)引擎及該系列(*MergeTree)中的其他引擎。
MergeTree 系列的引擎被設計用於插入極大量的數據到一張表當中。數據可以以數據片段的形式一個接着一個的快速寫入,數據片段在后台按照一定的規則進行合並。相比在插入時不斷修改(重寫)已存儲的數據,這種策略會高效很多。
主要特點:
- 存儲的數據按主鍵排序。
這使得你能夠創建一個小型的稀疏索引來加快數據檢索。
- 支持數據分區,如果指定了 分區鍵 的話。
在相同數據集和相同結果集的情況下 ClickHouse 中某些帶分區的操作會比普通操作更快。查詢中指定了分區鍵時 ClickHouse 會自動截取分區數據。這也有效增加了查詢性能。
- 支持數據副本。
ReplicatedMergeTree 系列的表提供了數據副本功能。更多信息,請參閱 數據副本 。
- 支持數據采樣。
需要的話,你可以給表設置一個采樣方法。
創建表的語法:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],
...
INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1,
INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'], ...]
[SETTINGS name=value, ...]
8.2 CollapsingMergeTree
該引擎繼承於 MergeTree,並在數據塊合並算法中添加了折疊行的邏輯。
CollapsingMergeTree 會異步的刪除(折疊)這些除了特定列 Sign 有 1 和 -1 的值以外,其余所有字段的值都相等的成對的行。沒有成對的行會被保留。更多的細節請看本文的折疊部分。
因此,該引擎可以顯著的降低存儲量並提高 SELECT 查詢效率。
創建表的語法:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = CollapsingMergeTree(sign)
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
CollapsingMergeTree 的參數
- sign — 類型列的名稱: 1 是“狀態”行,-1 是“取消”行。
列數據類型 — Int8。
折疊
數據
考慮你需要為某個對象保存不斷變化的數據的情景。似乎為一個對象保存一行記錄並在其發生任何變化時更新記錄是合乎邏輯的,但是更新操作對 DBMS 來說是昂貴且緩慢的,因為它需要重寫存儲中的數據。如果你需要快速的寫入數據,則更新操作是不可接受的,但是你可以按下面的描述順序地更新一個對象的變化。
在寫入行的時候使用特定的列 Sign。如果 Sign = 1 則表示這一行是對象的狀態,我們稱之為«狀態»行。如果 Sign = -1 則表示是對具有相同屬性的狀態行的取消,我們稱之為«取消»行。
例如,我們想要計算用戶在某個站點訪問的頁面頁面數以及他們在那里停留的時間。在某個時候,我們將用戶的活動狀態寫入下面這樣的行。
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
一段時間后,我們寫入下面的兩行來記錄用戶活動的變化。
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
第一行取消了這個對象(用戶)的狀態。它需要復制被取消的狀態行的所有除了 Sign 的屬性。
第二行包含了當前的狀態。
因為我們只需要用戶活動的最后狀態,這些行。
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
│ 4324182021466249494 │ 5 │ 146 │ -1 │
└─────────────────────┴───────────┴──────────┴──────┘
可以在折疊對象的失效(老的)狀態的時候被刪除。CollapsingMergeTree 會在合並數據片段的時候做這件事。
為什么我們每次改變需要 2 行可以閱讀算法段。
這種方法的特殊屬性:
- 寫入的程序應該記住對象的狀態從而可以取消它。«取消»字符串應該是«狀態»字符串的復制,除了相反的 Sign。它增加了存儲的初始數據的大小,但使得寫入數據更快速。
- 由於寫入的負載,列中長的增長陣列會降低引擎的效率。數據越簡單,效率越高。
- SELECT 的結果很大程度取決於對象變更歷史的一致性。在准備插入數據時要准確。在不一致的數據中會得到不可預料的結果,例如,像會話深度這種非負指標的負值。
算法
當 ClickHouse 合並數據片段時,每組具有相同主鍵的連續行被減少到不超過兩行,一行 Sign = 1(«狀態»行),另一行 Sign = -1 («取消»行),換句話說,數據項被折疊了。
對每個結果的數據部分 ClickHouse 保存:
1. 第一個«取消»和最后一個«狀態»行,如果«狀態»和«取消»行的數量匹配,且最后一個行是«狀態»行
2. 最后一個«狀態»行,如果«狀態»行比«取消»行多一個或一個以上。
3. 第一個«取消»行,如果«取消»行比«狀態»行多一個或一個以上。
4.在其他所有情況下,沒有行。
同樣,當“狀態”行比“取消”行多至少2個,或者“狀態”行比“取消”行多至少2個“取消”行時,合並繼續,但是ClickHouse將此情況視為邏輯錯誤並將其記錄在服務器日志。
如果多次插入同一數據,則會發生此錯誤。因此,折疊不應該改變統計數據的結果。
變化逐漸地被折疊,因此最終幾乎每個對象都只剩下了最后的狀態。
Sign 是必須的因為合並算法不保證所有有相同主鍵的行都會在同一個結果數據片段中,甚至是在同一台物理服務器上。ClickHouse 用多線程來處理 SELECT 請求,所以它不能預測結果中行的順序。如果要從 CollapsingMergeTree 表中獲取完全«折疊»后的數據,則需要聚合。
要完成折疊,請使用 GROUP BY 子句和用於處理符號的聚合函數編寫請求。例如,要計算數量,使用 sum(Sign) 而不是 count()。要計算某物的總和,使用 sum(Sign * x) 而不是 sum(x),並添加 HAVING sum(Sign) > 0 子句。
聚合體 count,sum 和 avg 可以用這種方式計算。如果一個對象至少有一個未被折疊的狀態,則可以計算 uniq 聚合。min 和 max 聚合無法計算,因為 CollaspingMergeTree 不會保存折疊狀態的值的歷史記錄。
如果你需要在不進行聚合的情況下獲取數據(例如,要檢查是否存在最新值與特定條件匹配的行),你可以在 FROM 從句中使用 FINAL 修飾符。這種方法顯然是更低效的。
使用示例
建表:
CREATE TABLE UAct
(
UserID UInt64,
PageViews UInt8,
Duration UInt8,
Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID;
插入數據:
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1);
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1),(4324182021466249494, 6, 185, 1);
我們使用兩次 INSERT 請求來創建兩個不同的數據片段。如果我們使用一個請求插入數據,ClickHouse 只會創建一個數據片段且不會執行任何合並操作。
獲取數據:
SELECT * FROM Uact;
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
我們看到了什么,哪里有折疊?
通過兩個 INSERT 請求,我們創建了兩個數據片段。SELECT 請求在兩個線程中被執行,我們得到了隨機順序的行。沒有發生折疊是因為還沒有合並數據片段。ClickHouse 在一個我們無法預料的未知時刻合並數據片段。
因此我們需要聚合:
SELECT
UserID,
sum(PageViews * Sign) AS PageViews,
sum(Duration * Sign) AS Duration
FROM UAct
GROUP BY UserID
HAVING sum(Sign) > 0
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │ 6 │ 185 │
|
|
|
如果我們不需要聚合並想要強制進行折疊,我們可以在 FROM 從句中使用 FINAL 修飾語。
SELECT * FROM UAct FINAL
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
這種查詢數據的方法是非常低效的。不要在大表中使用它。
如果想將一對sign 的值分別等於 1 和 -1,其他字段的值完全相同的數據抵消掉。
optimize table UAct final;
8.3 VersionedCollapsingMergeTree
這個引擎:
- 允許快速寫入不斷變化的對象狀態。
- 刪除后台中的舊對象狀態。 這顯着降低了存儲體積。
引擎繼承自 MergeTree 並將折疊行的邏輯添加到合並數據部分的算法中。 VersionedCollapsingMergeTree 用於相同的目的 折疊樹 但使用不同的折疊算法,允許以多個線程的任何順序插入數據。 特別是, Version 列有助於正確折疊行,即使它們以錯誤的順序插入。 相比之下, CollapsingMergeTree 只允許嚴格連續插入。
創建表:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = VersionedCollapsingMergeTree(sign, version)
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
引擎參數:
VersionedCollapsingMergeTree(sign, version)
- sign — 指定行類型的列名: 1 是一個 “state” 行, -1 是一個 “cancel” 划列數據類型應為 Int8.
- version — 指定對象狀態版本的列名。列數據類型應為 UInt*。
這個引擎和CollapsingMergeTree差不多,只是對CollapsingMergeTree引擎加了一個字段 version。
8.4 GraphiteMergeTree
該引擎用來對 Graphite數據進行瘦身及匯總。對於想使用CH來存儲Graphite數據的開發者來說可能有用。Graphite是一個開源的時序性數據庫。
如果不需要對Graphite數據做匯總,那么可以使用任意的CH表引擎;但若需要,那就采用 GraphiteMergeTree 引擎。它能減少存儲空間,同時能提高Graphite數據的查詢效率。
該引擎繼承自 MergeTree。
創建表:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
Path String,
Time DateTime,
Value <Numeric_type>,
Version <Numeric_type>
...
) ENGINE = GraphiteMergeTree(config_section)
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
含有Graphite數據集的表應該包含以下的數據列:
- 指標名稱(Graphite sensor),數據類型:String
- 指標的時間度量,數據類型: DateTime
- 指標的值,數據類型:任意數值類型
- 指標的版本號,數據類型: 任意數值類型
CH以最大的版本號保存行記錄,若版本號相同,保留最后寫入的數據。
以上列必須設置在匯總參數配置中。
GraphiteMergeTree 參數
- config_section - 配置文件中標識匯總規則的節點名稱
8.5 ReplacingMergeTree
該引擎和 MergeTree 的不同之處在於它會刪除排序鍵值相同的重復項。
數據的去重只會在數據合並期間進行。合並會在后台一個不確定的時間進行,因此你無法預先作出計划。有一些數據可能仍未被處理。盡管你可以調用 OPTIMIZE 語句發起計划外的合並,但請不要依靠它,因為 OPTIMIZE 語句會引發對數據的大量讀寫。
因此,ReplacingMergeTree 適用於在后台清除重復的數據以節省空間,但是它不保證沒有重復的數據出現。
創建表:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = ReplacingMergeTree([ver])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
ReplacingMergeTree 的參數:
- ver — 版本列。類型為 UInt*,Date 或 DateTime。可選參數。
在數據合並的時候,ReplacingMergeTree 從所有具有相同排序鍵的行中選擇一行留下:
- 如果 ver 列未指定,保留最后一條。
- 如果 ver 列已指定,保留 ver 值最大的版本。
使用示例
建表:
CREATE TABLE tb_rmt
(
id UInt8,
name String,
version UInt8
)
ENGINE=ReplacingMergeTree(version) ORDER BY (id,name);
插入數據:
insert into tb_rmt values (1,'Jason',1);
insert into tb_rmt values (1,'Jason',1);
insert into tb_rmt values (1,'Jason',2);
insert into tb_rmt values (2,'Tom',1);
insert into tb_rmt values (2,'Tom',1);
insert into tb_rmt values (2,'Tom',2);
獲取數據:
select * from tb_rmt order by id,name,version;
┌─────id────┬────name───┬──version──┐
│ 1 │ Jason │ 2 │
│ 2 │ Tom │ 2 │
└───────────┴───────────┴───────────┘
8.6 AggregatingMergeTree
該引擎繼承自 MergeTree,並改變了數據片段的合並邏輯。 ClickHouse 會將一個數據片段內所有具有相同主鍵(准確的說是 排序鍵)的行替換成一行,這一行會存儲一系列聚合函數的狀態。
可以使用 AggregatingMergeTree 表來做增量數據的聚合統計,包括物化視圖的數據聚合。
引擎使用以下類型來處理所有列:
AggregatingMergeTree 適用於能夠按照一定的規則縮減行數的情況。
創建表:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = AggregatingMergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[TTL expr]
[SETTINGS name=value, ...]
使用方法:
AggregatingMergeTree的語法比較復雜,需要結合物化視圖或ClickHouse的特殊數據類型AggregateFunction一起使用。在insert和select時,也有獨特的寫法和要求:寫入時需要使用-State語法,查詢時使用-Merge語法。
以下通過示例進行介紹。
示例一:配合物化視圖使用。
-- 建立明細表
CREATE TABLE visits
(
UserID UInt64,
CounterID UInt8,
StartDate Date,
Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID;
-- 對明細表建立物化視圖,該物化視圖對明細表進行預先聚合
-- 注意:預先聚合使用的函數分別為: sumState, uniqState。對應於寫入語法<agg>-State.
CREATE MATERIALIZED VIEW test.basic
ENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(StartDate) ORDER BY (CounterID, StartDate)
AS SELECT
CounterID,
StartDate,
sumState(Sign) AS Visits,
uniqState(UserID) AS Users
FROM test.visits
GROUP BY CounterID, StartDate;
-- 插入明細數據
INSERT INTO visits VALUES(0, 0, '2019-11-11', 1);
INSERT INTO visits VALUES(1, 1, '2019-11-12', 1);
-- 對物化視圖進行最終的聚合操作
-- 注意:使用的聚合函數為 sumMerge, uniqMerge。對應於查詢語法<agg>-Merge.
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
SELECT
StartDate,
sum(Visits),
uniq(Users)
FROM visits_agg_view
GROUP BY StartDate
ORDER BY StartDate;
示例二:配合特殊數據類型AggregateFunction使用。
-- 建立明細表
CREATE TABLE detail_table
( CounterID UInt8,
StartDate Date,
UserID UInt64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate);
-- 插入明細數據
INSERT INTO detail_table VALUES(0, '2019-11-11', 1);
INSERT INTO detail_table VALUES(1, '2019-11-12', 1);
-- 建立預先聚合表,
-- 注意:其中UserID一列的類型為:AggregateFunction(uniq, UInt64)
CREATE TABLE agg_table
( CounterID UInt8,
StartDate Date,
UserID AggregateFunction(uniq, UInt64)
) ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(StartDate)
ORDER BY (CounterID, StartDate);
-- 從明細表中讀取數據,插入聚合表。
-- 注意:子查詢中使用的聚合函數為 uniqState, 對應於寫入語法<agg>-State
INSERT INTO agg_table
select CounterID, StartDate, uniqState(UserID)
from detail_table
group by CounterID, StartDate;
-- 不能使用普通insert語句向AggregatingMergeTree中插入數據。
-- 本SQL會報錯:Cannot convert UInt64 to AggregateFunction(uniq, UInt64)
INSERT INTO agg_table VALUES(1, '2019-11-12', 1);
-- 從聚合表中查詢。
-- 注意:select中使用的聚合函數為uniqMerge,對應於查詢語法<agg>-Merge
SELECT uniqMerge(UserID) AS state
FROM agg_table
GROUP BY CounterID, StartDate;GROUP BY CounterID, StartDate;
8.7 SummingMergeTree
該引擎繼承自 MergeTree。區別在於,當合並 SummingMergeTree 表的數據片段時,ClickHouse 會把所有具有相同主鍵的行合並為一行,該行包含了被合並的行中具有數值數據類型的列的匯總值。如果主鍵的組合方式使得單個鍵值對應於大量的行,則可以顯著的減少存儲空間並加快數據查詢的速度。
我們推薦將該引擎和 MergeTree 一起使用。例如,在准備做報告的時候,將完整的數據存儲在 MergeTree 表中,並且使用 SummingMergeTree 來存儲聚合數據。這種方法可以使你避免因為使用不正確的主鍵組合方式而丟失有價值的數據。
建表:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) ENGINE = SummingMergeTree([columns])
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
。
SummingMergeTree 的參數
- columns - 包含了將要被匯總的列的列名的元組。可選參數。
所選的列必須是數值類型,並且不可位於主鍵中。
如果沒有指定 `columns`,ClickHouse 會把所有不在主鍵中的數值類型的列都進行匯總。
用法示例
考慮如下的表:
CREATE TABLE summtt
(
key UInt32,
value UInt32
)
ENGINE = SummingMergeTree()
ORDER BY key
向其中插入數據:
:) INSERT INTO summtt Values(1,1),(1,2),(2,1)
ClickHouse可能不會完整的匯總所有行,因此我們在查詢中使用了聚合函數 sum 和 GROUP BY 子句。
SELECT key, sum(value) FROM summtt GROUP BY key;
┌─key─┬─sum(value)─┐
│ 2 │ 1 │
│ 1 │ 3 │
└─────┴────────────┘
8.8 join引擎
建表語句:
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2],
) ENGINE = Join(join_strictness, join_type, k1[, k2, ...])
引擎參數:
- join_strictness – JOIN 限制,有ANY,ALL,SEMI或ANTI.
- join_type – INNER, LEFT, RIGHT, FULL
- k1[, k2, ...] – 進行JOIN 操作時 USING語句用到的key列
使用join_strictness 和 join_type 參數時不需要用引號, 例如, Join(ANY, LEFT, col1). 這些參數必須和進行join操作的表相匹配。否則,CH不會報錯,但是可能返回錯誤的數據。
表用法
示例:
|
創建左關聯表:
CREATE TABLE id_val(`id` UInt32, `val` UInt32) ENGINE = TinyLog;
INSERT INTO id_val VALUES (1,11)(2,12)(3,13)
創建 Join 右邊的表:
CREATE TABLE id_val_join(`id` UInt32, `val` UInt8) ENGINE = Join(ANY, LEFT, id);
INSERT INTO id_val_join VALUES (1,21)(1,22)(3,23)
表關聯:
SELECT * FROM id_val ANY LEFT JOIN id_val_join USING (id) SETTINGS join_use_nulls = 1
┌─id─┬─val─┬─id_val_join.val─┐
│ 1 │ 11 │ 21 │
│ 2 │ 12 │ ᴺᵁᴸᴸ │
│ 3 │ 13 │ 23 │
└────┴─────┴─────────────────┘
8.9 Distributed
Distributed引擎本身不存儲數據, 但可以在多個服務器上進行分布式查詢。讀是自動並行的。讀取時,遠程服務器表的索引(如果有的話)會被使用。
分布式引擎參數:
服務器配置文件中的集群名,
遠程數據庫名,
遠程表名,
數據分片鍵(可選),
策略名稱,它將用於存儲臨時文件以進行異步發送(可選)
例如:
Distributed(logs, default, hits[, sharding_key[, policy_name]])
將從集群logs中每個服務器上的default.hits表中讀取集群中所有服務器上的數據。數據不僅在遠程服務器上被讀取,而且被部分處理(在可能的范圍內)。例如,對於使用GROUP BY的查詢,數據將在遠程服務器上聚合,並且聚合函數的中間狀態將發送到請求者服務器。然后將進一步匯總數據。
9.3 節有該引擎的使用案例。