ClickHouse介紹(三)MergeTree系列表引擎


MergeTree系列表引擎

ClickHouse中最核心的引擎當屬MergeTree系列引擎,其中基礎表引擎為MergeTree,常用的表引擎還有ReplacingMergeTree、SummingMergeTree、AggregatingMergeTree、CollapsingMergeTree和VersionedCollapsingMergeTree。每一種MergeTree的變種,在繼承了基礎MergeTree的能力后,還增加了它獨有的特性。其名稱中的“合並”二字奠定了所有類型MergeTree的基因,它們的所有特殊邏輯,都是在觸發合並的過程中被激活的。接下來主要介紹它們各自的特點與使用方法。

 

1. ReplacingMergeTree

在MergeTree中,雖然有主鍵,但是它沒有唯一鍵的約束。也就是說,寫入數據的主鍵是可以重復的。但在某些場合下,用戶不希望表中有重復數據,ReplacingMergeTree就是為此場景而設計,它可以在合並分區時,刪除重復的數據條目。不過此方法僅是在“一定程度”上解決了重復數據的問題,稍后會對此作出解釋。

創建測試表:

CREATE TABLE replace_table
(
    `id` String,
    `code` String,
    `create_time` DateTime
)
ENGINE = ReplacingMergeTree
PARTITION BY toYYYYMM(create_time)
PRIMARY KEY id
ORDER BY (id, code)

這里ORDER BY 是去除重復數據的關鍵,排序鍵ORDER BY所聲明的表達式是后續判斷是否有重復數據的依據。在這個例子中,數據會基於id 和 code 兩個字段做去重。

分2個批次插入數據:

INSERT INTO replace_table VALUES 
('A001', 'C1', '2019-05-10 17:00:00'),
('A001', 'C2', '2019-05-14 17:00:00'),
('A001', 'C3', '2019-05-15 17:00:00')  

INSERT INTO replace_table VALUES 
('A001', 'C1', '2019-05-11 17:00:00'),
('A001', 'C100', '2019-05-12 17:00:00'),
('A001', 'C200', '2019-05-13 17:00:00')

# 查詢數據:
SELECT *
FROM replace_table

┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
└──────┴──────┴─────────────────────┘
┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-10 17:00:00 │
│ A001 │ C2 │ 2019-05-14 17:00:00 │
│ A001 │ C3 │ 2019-05-15 17:00:00 │
└──────┴──────┴─────────────────────┘

 

 

可以看到在表中是存在重復的id與<id, code>,使用 OPTIMIZE 命令強制觸發合並后再次查看數據條目:

SELECT *
FROM replace_table


┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C2 │ 2019-05-14 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
│ A001 │ C3 │ 2019-05-15 17:00:00 │
└──────┴──────┴─────────────────────┘

 

可以看到<id, code> 重復的條目已經被刪除了。在optimize強制觸發合並后,會按照<id, code> 進行分組,並保留分組內最后一條條目(觀察create_time日期字段)。

從測試結果可以知道,ReplacingMergeTree在去除重復數據時,是以ORDER BY排序鍵作為基准,而不是PRIMARY KEY。

前面提到ReplacingMergeTree僅在“一定程度上”解決了數據重復的問題,現在我們解釋這點。首先,再插入一條數據:

INSERT INTO replace_table VALUES ('A001', 'C1', '2019-08-10 17:00:00')

 

執行 OPTIMIZE TABLE 后再查看數據:

OPTIMIZE TABLE replace_table FINAL

SELECT *
FROM replace_table

┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-08-10 17:00:00 │
└──────┴──────┴─────────────────────┘
┌─id───┬─code─┬─────────create_time─┐
│ A001 │ C1 │ 2019-05-11 17:00:00 │
│ A001 │ C100 │ 2019-05-12 17:00:00 │
│ A001 │ C2 │ 2019-05-14 17:00:00 │
│ A001 │ C200 │ 2019-05-13 17:00:00 │
│ A001 │ C3 │ 2019-05-15 17:00:00 │
└──────┴──────┴─────────────────────┘

可以看到表中存在相同的<id, code> 行,但是並沒有執行去重。這是因為ReplacingMergeTree是以分區為單位進行合並,並在此時刪除重復數據。不同分區之間的重復數據無法進行刪除。所以ReplacingMergeTree僅是在“一定程度上“解決了數據重復問題。

 

1.2. 版本號

ReplacingMergeTree中可以根據版本號來決定:在對重復數據進行去重時,保留哪條數據。

例如以下建表語句:

CREATE TABLE replace_table
(
    `id` String,
    `code` String,
    `create_time` DateTime
)
ENGINE = ReplacingMergeTree(create_time)
PARTITION BY toYYYYMM(create_time)
ORDER BY id

可以看到在指定ReplacingMergeTree引擎時,傳入了一個列字段create_time。這個表基於id字段進行去重,並且使用create_time作為版本號。

在使用create_time作為版本號后,在對數據進行去重時,會保留id重復的條目中,create_time時間最長的一行。

 

1.3. ReplacingMergeTree總結

ReplacingMergeTree的處理邏輯為:

  1. 使用ORDER BY 排序鍵作為判斷數據是否重復的唯一鍵
  2. 只有在合並分區時才會觸發刪除重復數據的邏輯
  3. 僅有相同分區的數據會進行去重,無法跨分區進行去重
  4. 在進行去重操作時,由於分區內的數據已經基於ORDER BY進行了排序,所以能夠找到那些相鄰的重復數據
  5. 數據去重策略有2種:
    1. 如果沒有設置ver版本號,則保留同一組重復數據的最后一行
    2. 如果設置了ver版本號,則保留同一組重復數據中ver字段取值最大的那一行

 

2. SummingMergeTree

SummingMergeTree用於僅關系聚合結果,而不關心具體明細的場景。並且數據匯總條件是預先明確的(group by 的條件明確,不會隨意更改)。

SummingMergeTree可以在合並分區時,按照預先定義的條件,聚合匯總數據,將同一分區下的多行數據聚合成一行。這樣既減少了數據行,又降低了后續聚合查詢的開銷。

 

2.1. ORDER BY 與 PRIMARY KEY

在MergeTree中,數據會按照ORDER BY 的字段在分區內進行排序。主鍵索引也會按照PRIMARY KEY 表達式取值並排序。而ORDER BY可以指代主鍵,所以在一般情況下,只單獨聲明ORDER BY即可,此時數據排序與主鍵索引均相同。

如果需要同時定義ORDER BY與PRIMARY KEY,便是明確希望他倆不同。這種情況通常會在使用SummingMergeTree或AggregatingMergeTree時會出現,因為它們的聚合都是根據ORDER BY 進行的。由此可以引出2點原因:主鍵與聚合的條件定義分離,為修改聚合條件留下空間。

舉個例子,假設一張表使用的引擎為SummingMergeTree,包含6個字段,分別為A、B、C、D、E、F。如果需要按照A、B、C、D進行匯總,則有:

ORDER BY (A, B, C, D)

 

但是這樣的寫法也表示:表的PRIMARY KEY 被定義成A, B, C, D。若是在業務層面,僅需要對字段A進行查詢過濾,則應只使用A字段創建主鍵。所以更好的一種寫法是:

ORDER BY (A, B, C, D)

PRIMARY KEY A

 

如果同時聲明了ORDER BY 與 PRIMARY KEY,則MergeTree會強制要求PRIMARY KEY列字段必須是ORDER BY的前綴。

例如下面的定義是錯的:

ORDER BY (B, C)

PRIMARY KEY A

 

下面的是正確的:

ORDER BY (B, C)

PRIMARY KEY B

 

這種強制約束保障了即便在兩者定義不同的情況下,主鍵認識排序鍵的前提,不會出現索引與數據順序混亂的問題。

假設現在業務發生了細微的變化,需要減少字段,將先前的A、B、C、D改為按照A、B進行聚合,則可以按以下方式修改排序鍵:

ALTER TABLE table_name MODIFY ORDER BY (A, B)

 

在修改ORDER BY 時會有一些限制,只能在現有的基礎上減少字段。如果是新增排序字段,則只能添加通過ALTER ADD COLUMN 新增的字段。但是ALTER 是一種元數據的操作,修改成本很低,相比不能被修改的主鍵,這已經非常便利了。

 

2.2. SummingMergeTree的使用

SummingMergeTree引擎的聲明方式為:

ENGINE = SummingMergeTree((col1, col2, …))

 

這里col1,col2是選填參數,用於設置除主鍵外的其他數值類型字段,以指定被SUM聚合的列字段。若不指定此字段,則所有非主鍵的數值型字段均會進行SUM聚合。

下面舉例說明:

創建表:

CREATE TABLE summing_table
(
    `id` String,
    `city` String,
    `v1` UInt32,
    `v2` Float64,
    `create_time` DateTime
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(create_time)
PRIMARY KEY id
ORDER BY (id, city)

 

這里 order by 是關鍵配置,數據會以 order by 指定的列為維度進行聚合。

分批次插入數據:

INSERT INTO summing_table VALUES
('A001', 'wuhan', '10', '20', '2019-08-10 17:00:00')
 ('A001', 'jinzhou', '20', '30', '2019-08-10 17:00:00')

INSERT INTO summing_table VALUES
('A001', 'wuhan', '10', '20', '2019-02-10 17:00:00')
('A001', 'wuhan', '20', '30', '2019-08-20 17:00:00')

INSERT INTO summing_table VALUES
('A002', 'wuhan', '60', '50', '2019-10-10 17:00:00')

 

當前數據結果:

SELECT *
FROM summing_table


┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ wuhan │ 20 │ 30 │ 2019-08-20 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘
┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ wuhan │ 10 │ 20 │ 2019-02-10 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘
┌─id───┬─city────┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ jinzhou │ 20 │ 30 │ 2019-08-10 17:00:00 │
│ A001 │ wuhan   │ 10 │ 20 │ 2019-08-10 17:00:00 │
└──────┴─────────┴────┴────┴─────────────────────┘
┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A002 │ wuhan │ 60 │ 50 │ 2019-10-10 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘

 

執行 OPTIMIZE TABLE 后查看結果:

OPTIMIZE TABLE summing_table FINAL

SELECT *
FROM summing_table


┌─id───┬─city────┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ jinzhou │ 20 │ 30 │ 2019-08-10 17:00:00 │
│ A001 │ wuhan   │ 30 │ 50 │ 2019-08-10 17:00:00 │
└──────┴─────────┴────┴────┴─────────────────────┘
┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A001 │ wuhan │ 10 │ 20 │ 2019-02-10 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘
┌─id───┬─city──┬─v1─┬─v2─┬─────────create_time─┐
│ A002 │ wuhan │ 60 │ 50 │ 2019-10-10 17:00:00 │
└──────┴───────┴────┴────┴─────────────────────┘

可以看到,在不同分區內,數據以ORDER BY 的字段<id, city> 進行了聚合,聚合的字段為v1 與 v2。而非可聚合字段create_time 取的是同組內第一行數據的取值。

 

SummingMergeTree也支持嵌套類型的字段,在使用嵌套類型字段時,需要被SUM聚合的字段名稱必須以Map后綴結尾,例如:

CREATE TABLE summing_table_nested
(
    `id` String,
    `nestMap` Nested(id UInt32, key UInt32, val UInt64),
    `create_time` DateTime
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(create_time)
ORDER BY id

 

插入一條數據並查看結果:

INSERT INTO summing_table_nested VALUES
('A001', [1,1,2], [10,20,30], [40,50,60], '2019-08-10 17:00:00')

SELECT *
FROM summing_table_nested


┌─id───┬─nestMap.id─┬─nestMap.key─┬─nestMap.val─┬─────────create_time─┐
│ A001 │ [1,2]      │ [30,30]     │ [90,60]     │ 2019-08-10 17:00:00 │
└──────┴────────────┴─────────────┴─────────────┴─────────────────────┘

可以看到此結果是按照 id 進行了聚合,相同 nestMap.id 的條目在nestMap.key 與 nestMap.val 上進行了sum 聚合。

 

在使用嵌套數據類型的時候,也支持使用復合Key作為數據聚合的條件。為了使用復合Key,在嵌套類型的字段中,除第一個字段外,任何名稱是以Key、Id或Type為后綴結尾的字段,都將和第一個字段一起組合成為復合Key。例如將上面的例子中小寫key改為大寫Key:

(

    `id` String,

    `nestMap` Nested(id UInt32, Key UInt32, val UInt64),

    `create_time` DateTime

)

 

這樣數據就會以<id, Key> 作為條件進行聚合。

 

2.3. SummingMergeTree總結

SummingMergeTree的處理邏輯為:

  1. 用ORDER BY排序鍵作為聚合數據的條件Key
  2. 只有在合並分區的時候才會觸發聚合的邏輯
  3. 以分區為單位進行聚合,不同分區的數據之間不會聚合
  4. 如果在定義引擎時指定了column聚合列(非主鍵的數值類型字段),則sum聚合這些字段;如未指定,則聚合所有非主鍵的數值類型字段
  5. 在進行聚合時,由於分區內的數據已經基於ORDER BY排序,所以能夠找到相鄰且擁有相同聚合Key的數據
  6. 在聚合數據時,同一分區內,相同聚合Key的多行數據會合並為一行;聚合字段進行SUM運算;對於非聚合字段,使用第一行數據的值
  7. 支持嵌套結構,但列字段名必須以Map后綴結尾。嵌套類型中,默認以第1個字段作為聚合Key。除第1個字段外,任何名稱以Key、Id或Type為后綴結尾的字段,都將和第1個字段一起組成復合Key

 

3. AggregatingMergeTree

相信大家應該有了解過“數據立方體“(Cube)的概念,它在數倉領域非常常見。它的主要理念是:通過空間換取時間,對需要聚合的數據進行預計算,並將結果保存。在后續進行聚合查詢的時候,可以直接使用結果數據,提升查詢性能。

AggregatingMergeTree有些許“數據立方體“的意思,它能在合並分區的時候,按照預先定義的條件聚合數據。同時,根據預先定義的聚合函數,計算數據並以二進制的格式存入表內。

將同一分組下的多行數據聚合成一行,既減少了數據行,又降低了后續聚合查詢的開銷。可以說AggregatingMergeTree是SummingMergeTree的升級版,它們的許多設計思路是一致的,例如同時定義ORDER BY與PRIMARY KEY的原因和目的。但在使用方法上,兩者存在明顯差異。

AggregatingMergeTree沒有任何額外參數,在分區合並時,在每個數據分區內,會按照ORDER BY聚合。而使用何種聚合函數,以及針對哪些列字段計算,則是通過定義AggregateFunction數據類型實現的。

 

3.1. AggregatingMergeTree的使用

如下建表語句:

CREATE TABLE agg_table
(
    `id` String,
    `city` String,
    `code` AggregateFunction(uniq, String),
    `value` AggregateFunction(sum, UInt32),
    `create_time` DateTime
)
ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(create_time)
PRIMARY KEY id
ORDER BY (id, city)

上例中列字段id和city是聚合條件,等同於下面語義:

GROUP BY id, city

 

而 code 和 value 是聚合字段,其語義等同於:

UNIQ(code), SUM(value)

 

AggregateFunction是ClickHouse提供的一種特殊的數據類型,它能夠以二進制的形式存儲中間狀態結果。其使用方法也非常特殊,對於AggregateFunction類型的列字段,數據的寫入和查詢都與尋常不同:

  1. 在寫入數據時,需要調用 *State函數;
  2. 在查詢數據時,需要調用相應的*Merge函數

這里*表示定義時使用的聚合函數。

例如,在示例中定義的code和value使用了uniq和sum函數:

code AggregateFunction(uniq, String)

value AggregateFunction(sum, UInt32)

 

則在寫入數據時需要調用與uniq、sum對應的uniqState和sumState函數,並使用INSERT SELECT語法:

INSERT INTO TABLE agg_table
SELECT 'A000','wuhan',
uniqState('code1'),
sumState(toUInt32(100)),
'2019-09-10 17:00:00'

 

在查詢數據時,如果直接使用列名去訪問這里的code 與 value列時,結果為亂碼。這是因為它們的格式為二進制格式,在查詢時需要調用與uniq、sum對應的uniqMerge和sumMerge函數:

SELECT
    id,
    city,
    uniqMerge(code),
    sumMerge(value)
FROM agg_table
GROUP BY
    id,
    city

┌─id───┬─city──┬─uniqMerge(code)─┬─sumMerge(value)─┐
│ A000 │ wuhan │       1         │       100       │
└──────┴───────┴─────────────────┴─────────────────┘

 

以上 insert into 的方式並非是AggregatingMergeTree的常見用法,它更為常見的用法是結合物化視圖使用,將它作為物化視圖表的引擎。

 

3.2. 結合物化視圖使用

舉例說明,首先建立明細數據表(也就是俗稱的底表):

CREATE TABLE agg_table_basic
(
    `id` String,
    `city` String,
    `code` String,
    `value` UInt32
)
ENGINE = MergeTree()
PARTITION BY city
ORDER BY (id, city)

 

通常使用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

 

物化視圖使用AggregatingMergeTree表引擎,用於特定場景的數據查詢,相比MergeTree,它擁有更高的性能。

在新增數據時,面向的對象是底表MergeTree,例如:

INSERT INTO TABLE agg_table_basic VALUES
('A000','wuhan','code1',100),('A000','wuhan','code2',200),('A000','zhuhai','code1',200)
    
SELECT *
FROM agg_table_basic

┌─id───┬─city───┬─code──┬─value─┐
│ A000 │ zhuhai │ code1 │  200  │
└──────┴────────┴───────┴───────┘
┌─id───┬─city──┬─code──┬─value─┐
│ A000 │ wuhan │ code1 │  100  │
│ A000 │ wuhan │ code2 │  200  │
└──────┴───────┴───────┴───────┘

 

數據會自動同步到物化視圖,並按照AggregatingMergeTree引擎的規則進行處理:

SELECT
    id,
    city,
    sumMerge(value),
    uniqMerge(code)
FROM agg_view
GROUP BY
    id,
city

┌─id───┬─city───┬─sumMerge(value)─┬─uniqMerge(code)─┐
│ A000 │ zhuhai │     400         │        1        │
│ A000 │ wuhan  │     600         │        2        │
└──────┴────────┴─────────────────┴─────────────────┘

 

整個邏輯如下圖所示:

 

 

3.3. AggregatingMergeTree總結

AggregatingMergeTree的處理邏輯為:

  1. 使用ORDER BY 排序鍵作為聚合數據的條件key
  2. 使用AggregateFunction字段類型定義聚合函數的類型以及聚合的字段
  3. 只有在合並分區的時候才會觸發聚合計算的邏輯
  4. 以數據分區為單位聚合數據,僅在同一分區內能夠進行聚合,無法跨分區進行聚合
  5. 在進行數據計算時,由於分區內的數據已經基於ORDER BY進行排序,所以能夠找到那些相鄰且擁有相同聚合Key的數據
  6. 在聚合數據時,同一分區內,相同聚合Key的多行數據會合並成一行。對於那些非主鍵、非AggregateFunction類型字段,則會使用第一行數據的值
  7. AggregateFunction類型的字段使用二進制,在寫入數據時需要調用 *State函數;而在查詢數據時,需要調用相應的 *Merge 函數。其中 * 表示定義時使用的聚合函數
  8. AggregatingMergeTree通常作為物化視圖的表引擎,與普通MergeTree搭配使用

 

4. CollapsingMergeTree

CollapsingMergeTree是支持行級數據修改和刪除的引擎。在大數據領域,修改和刪除是代價非常高的操作,所以在實現時不會去修改源文件,而是將修改和刪除操作轉換為新增操作。

CollapsingMergeTree定義了一個sign標記位字段,用於記錄數據行的狀態:

  • sign為1:表示這是一行有效數據
  • sign為 -1:表示這行數據需要被刪除

 

在合並分區時,同一數據分區內,sign標記為1和-1的一組數據會被抵消刪除。這種相互抵消的操作,猶如將紙折疊一樣,所以這可能也是折疊合並樹(CollapsingMergeTree)名稱的由來。折疊過程如下圖所示:

 

 

4.1. CollapsingMergeTree的使用

建表:

CREATE TABLE collapse_table(
 id String,
 code Int32,
 create_time DateTime,
 sign Int8
 ) ENGINE = CollapsingMergeTree(sign)
PARTITION BY toYYYYMM(create_time)
ORDER BY id

 

同其他MergeTree 引擎一樣,CollapsingMergeTree也是通過ORDER BY 排序字段判斷數據唯一性;除此之外,CollapsingMergeTree還支持其他2種特殊操作:修改與刪除。

下面舉例:

--插入原始數據,后續會被修改
INSERT INTO TABLE collapse_table VALUES ('A000', 100, '2019-02-20 00:00:00', 1)

--原始數據的鏡像數據,ORDER BY 字段與原條目必須相同(其他字段可以不同),sign取 -1,
--它會和原始條目折疊
INSERT INTO TABLE collapse_table VALUES ('A000', 100, '2019-02-20 10:00:00', -1)

--強制OPTIMIZE 合並后查看結果,可以看到結果為空
SELECT *
FROM collapse_table

0 rows in set. Elapsed: 0.001 sec.

--修改后的數據,sign為1
INSERT INTO TABLE collapse_table VALUES ('A000', 120, '2019-02-20 00:00:00', 1)

--檢查結果
SELECT *
FROM collapse_table


┌─id───┬─code─┬─────────create_time─┬─sign─┐
│ A000 │ 120  │ 2019-02-20 00:00:00 │   1  │
└──────┴──────┴─────────────────────┴──────┘

 

CollapsingMergeTree在折疊數據時,遵循以下規則:

  1. 如果sign=1比sign=-1的數據多一行,則保留最后一行sign=1的數據
  2. 如果sign=-1比sign=1的數據多一行,則保留第一行sign=-1的數據
  3. 如果sign=1和sign=-1的數據行一樣多,且最后一行為sign=1,則保留第一行sign=-1和最后一行sign=1的數據
  4. 如果sign=1和sign=-1的數據行一樣多,且最后一行為sign=-1,則什么也不保留
  5. 其余情況ClickHouse會打印警告日志,但不會報錯,此時查詢結果未知

 

4.2. CollapsingMergeTree注意事項

1. 折疊數據並非實時觸發,而是在分區合並時在進行。所以在分區合並前,舊數據仍對用戶可見。解決此問題的方式有2種:

1.1. 在查詢數據前,執行 OPTIMIZE TABLE table_name FINAL 強制觸發分區合並,但此方法效率很低,在生產環境慎用。

1.2. 改變查詢方式。以collapse_table為例,假設原始SQL為:

SELECT id, SUM(code), COUNT(code), AVG(code), uniq(code)

FROM collapse_table

GROUP BY id

 

則可以改寫為:

SELECT id, SUM(code * sign), COUNT(code * sign), AVG(code * sign), uniq(code * sign)

FROM collapse_table

GROUP BY id

HAVING SUM(sign) > 0

 

2. 只有相同分區內的數據才可能被折疊,不過刪除或修改數據時,一般分區規則都是一致的。

 

3. CollapsingMergeTree最大的限制在於:對於寫入數據的順序有非常嚴格的要求。前面例子我們可以看到,若是先寫sign=1再寫sign=-1,則沒有任何問題。但若是先寫sign=-1再寫sign=1 的話呢:

INSERT INTO TABLE collapse_table VALUES('A000', 101, '2019-02-20 00:00:00', -1)

INSERT INTO TABLE collapse_table VALUES('A000', 102, '2019-02-20 00:00:00', 1)

OPTIMIZE TABLE collapse_table FINAL

select * from collapse_table;

┌─id───┬─code─┬─────────create_time─┬─sign─┐
│ A000 │ 101  │ 2019-02-20 00:00:00 │   -1 │
│ A000 │ 102  │ 2019-02-20 00:00:00 │   1  │
└──────┴──────┴─────────────────────┴──────┘

可以看到在順序置換后,並沒有進行折疊。若是在單線程寫入時,順序是可以保證的,但是在多線程並行寫入就不一定了。為了解決這個問題,ClickHouse另外提供了一個名為VersionedCollapsingMergeTree的表引擎。

 

5. VersionedCollapsingMergeTree

VersionedCollapsingMergeTree表引擎的功能與CollapsingMergeTree完全相同,不同之處在於:它對數據的寫入順序沒有要求,同一分區內,任意順序的數據都能完成折疊操作。通過版本號實現。

在定義VersionedCollapsingMergeTree時,除了需要指定sign標記字段外,還需要指定一個UInt8類型的ver版本號字段。例如:

CREATE TABLE ver_collapse_table(
 id String,
 code Int32,
 create_time DateTime,
 sign Int8,
 ver UInt8
) ENGINE = VersionedCollapsingMergeTree(sign,ver)
PARTITION BY toYYYYMM(create_time)
ORDER BY id

 

VersionedCollapsingMergeTree是如何使用版本號的呢?很簡單,在定義了ver字段后,它會自動將此字段作為排序條件增加到ORDER BY 后面。例如上面定義的表,在每個數據分區內,數據均會以 ORDER BY id, ver DESC 進行排序。所以無論寫入時數據的順序如何,在折疊處理時,都能回到正確的順序。

例如:

刪除一行數據:

INSERT INTO TABLE ver_collapse_table VALUES('A000', 101, '2019-02-20 00:00:00', -1, 1)

INSERT INTO TABLE ver_collapse_table VALUES('A000', 101, '2019-02-20 00:00:00', 1, 1)

OPTIMIZE TABLE ver_collapse_table FINAL

SELECT *
FROM ver_collapse_table

0 rows in set. Elapsed: 0.001 sec.

 

修改數據:

INSERT INTO TABLE ver_collapse_table VALUES('A000', 101, '2019-02-20 00:00:00', -1, 1)

INSERT INTO TABLE ver_collapse_table VALUES('A000', 102, '2019-02-20 00:00:00', 1, 1)

INSERT INTO TABLE ver_collapse_table VALUES('A000', 103, '2019-02-20 00:00:00', 1, 2)

OPTIMIZE TABLE ver_collapse_table;

select * from ver_collapse_table;


┌─id───┬─code─┬─────────create_time─┬─sign─┬─ver─┐
│ A000 │ 103  │ 2019-02-20 00:00:00 │   1  │  2  │
└──────┴──────┴─────────────────────┴──────┴─────┘

 

6. MergeTree家族關系總結

MergeTree表引擎向下派生出6個變種表引擎:

 

 

7. 組合關系

上面是MergeTree關系,下面介紹ReplicatedMergeTree系列。

ReplicatedMergeTree與普通的MergeTree的區別:

 

 

虛線部分是MergeTree的能力邊界,而ReplicatedMergeTree是在MergeTree能力的基礎之上增加了分布式協同的能力。它借助了ZooKeeper的消息日志廣播功能,實現了副本實例之間的數據同步功能。

ReplicatedMergeTree系列通過組合關系來理解,如下圖所示:

 

 

在為MergeTree加上Replicated前綴后,又可以組合出7種新的表引擎,這些ReplicatedMergeTree擁有副本協同的能力。之后會詳細說明。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM