11_CollapsingMergeTree,(State行和Cancel行匹配示例),聚合查詢,Cancel狀態行取反的聚合查詢
技術標籤:# ClickHouse
10.CollapsingMergeTree
10.1.CollapsingMergeTree
10.2.案例(State行和Cancel行匹配示例)
10.3.示例2:聚合查詢
10.4.示例3:Cancel狀態行取反的聚合查詢
10.CollapsingMergeTree
10.1.CollapsingMergeTree
1、在排序鍵(ORDER BY)的所有欄位都相同的條件下,如果特定的欄位具有1和-1的值,CollapsingMergeTree將非同步刪除(摺疊)成對的行。沒有配對的行將保留。
2、能顯著降低儲存空間並提升SELECT查詢的效率。
3、變相的實現了資料的更新和刪除邏輯。
指定表引擎:
CollapsingMergeTree(sign)
引數:sign,標識行型別的列名稱,1是狀態行,-1是取消行。列的資料型別是Int8。
使用特定的列Sign,如果Sign=1,則表示該行是物件的狀態,稱之為"狀態"行。如果Sign=-1,則表示取消具有屬性的物件的狀態,稱之為"取消"行。
例如,計算使用者在某個站點訪問的頁面數以及停留的時長。在某個時刻,將使用者的活動狀態寫入下面的行:
一段時間後,將使用者活動的變化寫入以下兩行:
第一行取消物件(使用者)的先前狀態,它應該複製被取消狀態行的排序鍵欄位,欄位sign設定為-1標 識取消狀態。
取消狀態的行包含:排序鍵欄位的拷貝和相反的Sign值。 取消狀態的行增加了儲存的大小,但是卻可以 快速寫入資料。
合併演算法
當ClickHouse合併資料片段時,具有相同排序鍵的每一組連續行被縮減為不超過兩行,一行的Sign=1, 另一行的Sign=-1。換句話說,條目將摺疊。
- 如果”state”行與”cancel”行的數目匹配,且最後一行是”state”行,則保留第一個”cancel”和最後一個”state”行。
- 如果”state”行比”cancel”行的數目多,則保留最後一個”state”行。
- 如果”cancel”行比”state”行的數目多,則保留第一個”cancel”行。
- 其它情況,不保留行。
Note:當”state”行比”cancel”行數目至少多2個,或者”cancel”行比”state”行多至少2個時,合併將繼續,但是ClickHouse將此情況視為邏輯錯誤並將其在server的日誌記錄下來。如果同一份資料被插入了多次,則會發生此錯誤。
聚合統計
合併演算法不能保證所有具有相同排序鍵的行都位於相同的結果資料片段中,甚至位於同一個物理伺服器上。 ClickHouse使用多執行緒處理SELECT查詢,並且無法預測結果中的行順序。如果需要從CollapsingMergeTree表中完全”摺疊”資料,則需要結合Sign欄位使用聚合。
例如:計算使用,使用sum(Sign)而不是count()。計算sum,使用sum(Sign * x)而不是sum(x),以此類推。
並且需要新增HAVING sum(Sign) > 0.
聚合的count、sum和avg可以通過這種方式計算,如果一個物件的至少一個狀態未摺疊,則可以計算聚合 uniq。無法計算聚合的最小值和最大值,因為CollapsingMergeTree不會保存摺疊狀態的歷史記錄。
如果要提取資料但不想使用聚合,則可以對FROM子句使用FINAL修飾符,這種方法效率明顯不高。
10.2.案例(State行和Cancel行匹配示例):
示例:State行和Cancel行匹配示例
驗證:如果"state"行與"cancel"行的數目匹配,且最後一行是"state"行,則保留第一個"cancel"行和最後一個"state"行。
建表語句:
CREATE TABLE UAct
(
UserID UInt64,
PageViews UInt8,
Duration UInt8,
Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID;
首先清空表:
truncate table UAct;
執行如下的資料插入語法:
INSERT INTO UAct VALUES (4324182021466249494, 1, 11, -1),(4324182021466249494, 2, 12, -1);
INSERT INTO UAct VALUES (4324182021466249494, 3, 13, 1);
INSERT INTO UAct VALUES (4324182021466249494, 4, 14, 1);
檢視資料:
xxxx2 :) select * from UAct;
SELECT *
FROM UAct
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 4 │ 14 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 1 │ 11 │ -1 │
│ 4324182021466249494 │ 2 │ 12 │ -1 │
└─────────────────────┴───────────┴──────────┴──────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 3 │ 13 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
4 rows in set. Elapsed: 0.010 sec.
xxxx2 :)
執行計劃外的片段合併操作:
xxxx2 :) optimize table UAct;
OPTIMIZE TABLE UAct
Ok.
0 rows in set. Elapsed: 0.004 sec.
xxxx2 :) select * from UAct;
SELECT *
FROM UAct
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 1 │ 11 │ -1 │
│ 4324182021466249494 │ 4 │ 14 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
2 rows in set. Elapsed: 0.008 sec.
xxxx2 :)
從示例中可以觀察到,最終的資料片段保留了第一個"cancel"行和最後一個"state"行。
10.3.示例2:聚合查詢
示例資料:
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
建表:
DROP TABLE UAct;
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語句,建立了兩個不同的資料片段。如果使用一個INSERT語句,ClickHouse將建立一個數據片段,並且將永遠不會執行任何合併。
查詢資料:
xxxx2 :) select * from UAct;
SELECT *
FROM UAct
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
3 rows in set. Elapsed: 0.012 sec.
xxxx2 :)
通過兩個INSERT語句,建立了兩個資料片段。SELECT查詢是在兩個執行緒中執行的,我們得到了隨機順序的行。由於尚未合併資料片段,摺疊還未發生。
我們無法預測ClickHouse在何時執行資料片段的合併。因此,我們需要使用聚合:
SELECT
UserID,
sum(PageViews * Sign) AS PageViews,
sum(Duration * Sign) AS Duration
FROM UAct
GROUP BY UserID
HAVING sum(Sign) > 0;
如果不使用聚合,可以對FROM子句使用FINAL修飾符進行強制合併:
select * from UAct FINAL;
效果如下:
xxxx2 :) select * from UAct FINAL;
SELECT *
FROM UAct
FINAL
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
1 rows in set. Elapsed: 0.010 sec.
xxxx2 :)
10.4.示例3:Cancel狀態行取反的聚合查詢
示例資料:
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │ 5 │ 146 │ 1 │
│ 4324182021466249494 │ -5 │ -146 │ -1 │
│ 4324182021466249494 │ 6 │ 185 │ 1 │
└─────────────────────┴───────────┴──────────┴──────┘
這個方法的核心思想是僅考慮關鍵欄位,在"cancel"行中,可以指定負數,這些負數的值等於行的前一個版本的關鍵欄位值的取反,這樣在求和時就可以不使用Sign列。對於這種方法,必須更改PageViews和Duration欄位的資料型別,從UIn8改成Int16。
CREATE TABLE UAct
(
UserID UInt64,
PageViews Int16,
Duration Int16,
Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID;
插入資料,並測試:
insert into UAct values(4324182021466249494, 5, 146, 1);
insert into UAct values(4324182021466249494, -5, -146, -1);
insert into UAct values(4324182021466249494, 6, 185, 1);