1. 程式人生 > 其它 >clickhouse(十)物化檢視使用一

clickhouse(十)物化檢視使用一

我們喜歡ClickHouse的物化檢視. 物化檢視可以實現聚合計算, 從Kafka讀取資料, 實現最後點查詢(last point queries)以及重組表主鍵索引和排序順序. 除了這些功能之外, 物化檢視可以在大量節點上很好地擴縮, 並可以處理大型資料集. 它們是ClickHouse的獨特功能之一.

在計算領域, 強大的功能至少意味著一點點複雜性. 這篇由兩部分組成的文章通過解釋物化檢視的工作原理來填補空白, 從而使初學者也可以有效地使用它們. 我們將通過幾個詳細的示例, 您可以根據自己的使用進行調整. 在此過程中, 我們將探索用於建立檢視的語法的確切含義, 並讓您深入瞭解ClickHouse在做什麼. 示例是完全自包含的, 因此您可以將它們複製/貼上到clickhouse-client中並自己執行它們.

How Materialized Views Work: Computing Sums

ClickHouse物化檢視自動在表之間轉換資料. 它們類似於觸發器, 對插入的行執行查詢並將結果存入第二個表. 讓我們看一個基本的例子. 假設我們有一個記錄使用者下載的表, 如下所示.

1
2
3
4
5
6
7
CREATE TABLE download (
when DateTime,
userid UInt32,
bytes Float32
) ENGINE=MergeTree
PARTITION BY toYYYYMM(when)
ORDER BY (userid, when)

我們希望跟蹤每個使用者的每日下載. 讓我們看看如何用一個查詢來做到這一點. 首先, 我們需要為單個使用者向表中新增一些資料.

1
2
3
4
5
6
7
INSERT INTO download
SELECT
now() + number * 60 as when,
25,
rand() % 100000000
FROM system.numbers
LIMIT 5000

接下來, 讓我們執行一個查詢來顯示該使用者的每日下載. 當新增新使用者時, 這也將正常工作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SELECT 
toStartOfDay(when) AS day,
userid,
count() AS downloads,
sum(bytes) AS bytes
FROM download
GROUP BY
userid,
day
ORDER BY
userid ASC,
day ASC

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:00 │ 25 │ 736 │ 36722522631 │
2020-08-19 00:00:00 │ 25 │ 1440 │ 73305428060 │
2020-08-20 00:00:00 │ 25 │ 1440 │ 73183910537 │
2020-08-21 00:00:00 │ 25 │ 1384 │ 70059352697 │
└─────────────────────┴────────┴───────────┴─────────────┘

我們可以通過每次執行查詢以互動方式為應用程式計算這些每日總數, 但是對於大型表, 提前計算它們將更快, 更節省資源. 因此, 最好將結果放在單獨的表格中, 該表格可以連續跟蹤每天每個使用者的下載總數. 我們可以使用以下物化檢視來做到這一點.

1
2
3
4
5
6
7
8
9
10
11
CREATE MATERIALIZED VIEW download_daily_mv
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(day) ORDER BY (userid, day)
POPULATE
AS SELECT
toStartOfDay(when) AS day,
userid,
count() as downloads,
sum(bytes) AS bytes
FROM download
GROUP BY userid, day

這裡有三件重要的事情需要注意. 首先, 物化檢視定義允許類似於CREATE TABLE的語法, 這是有意義的, 因為這個命令將實際建立一個隱藏的目標表(.inner表)來儲存檢視資料. 我們使用的ClickHouse引擎旨在使計算和計數變得簡單:SummingMergeTree. 它是用於計算聚合的物化檢視的推薦引擎.

其次, 檢視定義包含關鍵字POPULATE. 這告訴ClickHouse將download表中的現有資料插入物化檢視. 我們稍後會更多地討論automatic population.

需要注意, 在POPULATE填充歷史資料的期間, 新進入的這部分資料會被忽略掉, 所以如果對準確性要求非常高, 應慎用

第三, 檢視定義包含一個SELECT語句, 該語句定義在載入檢視時如何轉換資料. 這個查詢在表中的新資料上執行, 以計算每天每個使用者id的下載數量和總位元組數. 它本質上與我們以互動方式執行的查詢相同, 只是在本例中, 結果將放在隱藏的目標表(.inner表)中. 我們可以跳過排序, 因為檢視定義已經確保了排序順序.

現在, 我們從物化檢視中查詢資料

1
2
3
4
5
6
7
8
9
10
SELECT * FROM download_daily_mv
ORDER BY day, userid
LIMIT 5

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:00 │ 25 │ 736 │ 36722522631 │
2020-08-19 00:00:00 │ 25 │ 1440 │ 73305428060 │
2020-08-20 00:00:00 │ 25 │ 1440 │ 73183910537 │
2020-08-21 00:00:00 │ 25 │ 1384 │ 70059352697 │
└─────────────────────┴────────┴───────────┴─────────────┘

這為我們提供了與先前查詢完全相同的答案. 原因是上面介紹的POPULATE關鍵字. 它確保源表中的現有資料自動載入到檢視中. 不過, 有一個重要警告:如果在填充檢視時插入了新資料, ClickHouse將會丟失它們. 在本系列的第二部分中, 我們將展示如何手動插入資料並避免資料遺漏的問題.

現在, 嘗試使用其他使用者向表中新增更多資料.

1
2
3
4
5
6
7
INSERT INTO download
SELECT
now() + number * 60 as when,
22,
rand() % 100000000
FROM system.numbers
LIMIT 5000

如果您從例項化檢視中進行選擇, 您將看到它現在具有使用者ID 22和25的總數. 請注意, 一旦INSERT完成, 將立即填充新資料. 這是ClickHouse例項化檢視的重要功能, 這使其對於實時分析非常有用.

下面是查詢和新結果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SELECT *
FROM download_daily_mv
ORDER BY
userid ASC,
day ASC

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:00 │ 22 │ 416 │ 21063519801 │
2020-08-19 00:00:00 │ 22 │ 1440 │ 71523929305 │
2020-08-20 00:00:00 │ 22 │ 1440 │ 70435459582 │
2020-08-21 00:00:00 │ 22 │ 1440 │ 70725673036 │
2020-08-22 00:00:00 │ 22 │ 264 │ 13826466067 │
└─────────────────────┴────────┴───────────┴─────────────┘
┌─────────────────day─┬─userid─┬─downloads─┬────────bytes─┐
2020-08-18 00:00:00 │ 25 │ 1467 │ 75441118166 │
2020-08-19 00:00:00 │ 25 │ 2880 │ 144811386193 │
2020-08-20 00:00:00 │ 25 │ 2880 │ 145138479865 │
2020-08-21 00:00:00 │ 25 │ 2773 │ 138488485955 │
└─────────────────────┴────────┴───────────┴──────────────┘

作為練習, 您可以對源表執行原始查詢, 以確認它與物化檢視中的總數相匹配.

作為最後一個示例, 讓我們使用物化檢視按月彙總. 在本例中, 我們將物化檢視視為一個普通表, 按月分組, 如下所示. 我們添加了WITH TOTALS子句, 它列印一個方便的聚合的總和.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SELECT 
toStartOfMonth(day) AS month,
userid,
sum(downloads),
sum(bytes)
FROM download_daily_mv
GROUP BY
userid,
month
WITH TOTALS
ORDER BY
userid ASC,
month ASC

┌──────month─┬─userid─┬─sum(downloads)─┬───sum(bytes)─┐
2020-08-01 │ 22 │ 5000 │ 247575047791 │
2020-08-01 │ 25 │ 10000 │ 503879470179 │
└────────────┴────────┴────────────────┴──────────────┘

Extremes:
┌──────month─┬─userid─┬─sum(downloads)─┬───sum(bytes)─┐
0000-00-00 │ 0 │ 15000 │ 751454517970 │
└────────────┴────────┴────────────────┴──────────────┘

從前面的示例中, 我們可以清楚地看到例項化檢視如何正確地彙總源資料中的資料(how the materialized view correctly summarizes data from the source data). 如上例所示, 我們甚至可以“summarize the summaries”. 那麼幕後到底發生了什麼? 下圖說明了資料的邏輯流.

logical

如圖所示, 原表上INSERT的值被轉換並應用於隱藏的目標表(.inner表). 要填充檢視(To populate the view), 您要做的就是在源表中插入資料.

您可以從隱藏的目標表(.inner表)和物化檢視中進行查詢. 實際上ClickHouse就是將查詢路由到建立物化檢視時自動建立的internel表中的.

圖中還有另外一件重要的事情需要注意. 物化檢視建立一個具有特殊名稱私有表來儲存資料. 如果您通過輸入DROP TABLE download_daily_mv刪除物化檢視, 則私有表也會被刪除. 如果需要更改檢視, 則需要將其刪除並使用新資料重新建立

1
2
3
4
5
6
7
8
>SHOW TABLES

>┌─name─────────────────────┐
>│ .inner.download_daily_mv │
>│ download │
>│ download_daily_mv │
>└──────────────────────────┘

.inner.download_daily_mv就是 internel表或叫私有表

Wrap-up - 總結

我們剛剛審閱的示例使用SummingMergeTree建立一個檢視以累加每日使用者下載量. 我們從物化檢視對SELECT使用了標準SQL語法. 這是SummingMergeTree引擎的特殊功能, 僅適用於總和和計數. 對於其他型別的聚合, 我們需要使用其他方法.

另外, 我們的示例使用POPULATE關鍵字將現有表資料釋出到檢視建立的私有目標表(.inner表)中. 如果在填充檢視時到達新的INSERT行, ClickHouse將錯過它們. 當您是唯一使用資料集的人時, 此限制很容易解決, 但對於不斷載入資料的生產系統來說是個問題. 此外, 刪除檢視後, 專用表也會消失. 這使得很難更改檢視以適應源表中的架構更改.

在下一篇文章中, 我們將展示如何建立物化檢視來計算其他型別的聚合, 比如平均值或最大值/最小值. 我們還將展示如何顯式定義目標表(.inner表), 並使用我們自己的SQL語句手動將資料載入到其中. 我們還將簡要介紹模式遷移(schema migration). 同時, 我們希望您喜歡這一簡要介紹, 並發現示例有用.

實測及問題

刪除基表, 物化檢視仍然可以查詢

這是符合預期的, 因為物化檢視是儲存了資料的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
bj2-all-clickhouse-test-02 :) drop table download;

DROP TABLE download

Ok.

0 rows in set. Elapsed: 0.016 sec.

bj2-all-clickhouse-test-02 :) show tables;

SHOW TABLES

┌─name─────────────────────┐
│ .inner.download_daily_mv │
│ download_daily_mv │
│ sbtest │
│ sbtest_local │
└──────────────────────────┘

4 rows in set. Elapsed: 0.001 sec.

bj2-all-clickhouse-test-02 :) select * from download_daily_mv;

SELECT *
FROM download_daily_mv

┌─────────────────day─┬─userid─┬─downloads─┬────────bytes─┐
2020-08-18 00:00:00 │ 25 │ 1467 │ 75441118166 │
2020-08-19 00:00:00 │ 25 │ 2880 │ 144811386193 │
2020-08-20 00:00:00 │ 25 │ 2880 │ 145138479865 │
2020-08-21 00:00:00 │ 25 │ 2773 │ 138488485955 │
└─────────────────────┴────────┴───────────┴──────────────┘
┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:00 │ 22 │ 416 │ 21063519801 │
2020-08-19 00:00:00 │ 22 │ 1440 │ 71523929305 │
2020-08-20 00:00:00 │ 22 │ 1440 │ 70435459582 │
2020-08-21 00:00:00 │ 22 │ 1440 │ 70725673036 │
2020-08-22 00:00:00 │ 22 │ 264 │ 13826466067 │
└─────────────────────┴────────┴───────────┴─────────────┘

9 rows in set. Elapsed: 0.008 sec.

資料在分割槽合併時聚合

重新建立download表和物化檢視

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
SELECT 
toStartOfDay(when) AS day,
userid,
count() AS downloads,
sum(bytes) AS bytes
FROM download
GROUP BY
userid,
day
ORDER BY
userid ASC,
day ASC

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:00 │ 25 │ 387 │ 19855551536 │
2020-08-19 00:00:00 │ 25 │ 1440 │ 73316885071 │
2020-08-20 00:00:00 │ 25 │ 1440 │ 72322018002 │
2020-08-21 00:00:00 │ 25 │ 1440 │ 71675677053 │
2020-08-22 00:00:00 │ 25 │ 293 │ 14125612942 │
└─────────────────────┴────────┴───────────┴─────────────┘



SELECT *
FROM download_daily_mv

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:00 │ 25 │ 387 │ 19855551536 │
2020-08-19 00:00:00 │ 25 │ 1440 │ 73316885071 │
2020-08-20 00:00:00 │ 25 │ 1440 │ 72322018002 │
2020-08-21 00:00:00 │ 25 │ 1440 │ 71675677053 │
2020-08-22 00:00:00 │ 25 │ 293 │ 14125612942 │
└─────────────────────┴────────┴───────────┴─────────────┘

再次向基表中插入資料, 然後檢視物化檢視中的資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
INSERT INTO download SELECT 
now() + (number * 60) AS when,
25,
rand() % 100000000
FROM system.numbers
LIMIT 5000



SELECT *
FROM download_daily_mv

┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:00 │ 25 │ 387 │ 19855551536 │
2020-08-19 00:00:00 │ 25 │ 1440 │ 73316885071 │
2020-08-20 00:00:00 │ 25 │ 1440 │ 72322018002 │
2020-08-21 00:00:00 │ 25 │ 1440 │ 71675677053 │
2020-08-22 00:00:00 │ 25 │ 293 │ 14125612942 │
└─────────────────────┴────────┴───────────┴─────────────┘
┌─────────────────day─┬─userid─┬─downloads─┬───────bytes─┐
2020-08-18 00:00:00 │ 25 │ 385 │ 18771807859 │
2020-08-19 00:00:00 │ 25 │ 1440 │ 73675739078 │
2020-08-20 00:00:00 │ 25 │ 1440 │ 70555293899 │
2020-08-21 00:00:00 │ 25 │ 1440 │ 70773685728 │
2020-08-22 00:00:00 │ 25 │ 295 │ 14750186600 │
└─────────────────────┴────────┴───────────┴─────────────┘

可以看到物化檢視中的資料並沒有”全部聚合完整”

檢視物化檢視private表分割槽情況

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
bj2-all-clickhouse-test-02 :) select * from system.parts where table='.inner.download_daily_mv'\G

SELECT *
FROM system.parts
WHERE table = '.inner.download_daily_mv'

Row 1:
──────
partition: 202008
name: 202008_1_1_0
part_type: Wide
active: 1
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:33:51
remove_time: 0000-00-00 00:00:00
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 1
max_block_number: 1
level: 0
data_version: 1
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_1_1_0/
hash_of_all_files: f4b55a88dac393d25ffe1c703cca4f6d
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: e18b006f608580db132ed90adb46902f

Row 2:
──────
partition: 202008
name: 202008_2_2_0
part_type: Wide
active: 1
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:35:13
remove_time: 0000-00-00 00:00:00
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 2
max_block_number: 2
level: 0
data_version: 2
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_2_2_0/
hash_of_all_files: 049d090ea65c24be8544ab86846ea9fa
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: 9ffea93e6b9bc375c7f04374b4a4a6a9

2 rows in set. Elapsed: 0.002 sec.

可以看到, 有兩個active分割槽202008_1_1_0,202008_2_2_0

我們手動OPTIMIZE嘗試合併分割槽

1
2
3
4
5
6
7
bj2-all-clickhouse-test-02 :) optimize table download_daily_mv;

OPTIMIZE TABLE download_daily_mv

Ok.

0 rows in set. Elapsed: 0.004 sec.

再次查詢物化檢視資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bj2-all-clickhouse-test-02 :) select * from download_daily_mv;

SELECT *
FROM download_daily_mv

┌─────────────────day─┬─userid─┬─downloads─┬────────bytes─┐
2020-08-18 00:00:00 │ 25 │ 772 │ 38627359395 │
2020-08-19 00:00:00 │ 25 │ 2880 │ 146992624149 │
2020-08-20 00:00:00 │ 25 │ 2880 │ 142877311901 │
2020-08-21 00:00:00 │ 25 │ 2880 │ 142449362781 │
2020-08-22 00:00:00 │ 25 │ 588 │ 28875799542 │
└─────────────────────┴────────┴───────────┴──────────────┘

5 rows in set. Elapsed: 0.001 sec.

檢視private表分割槽情況

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
bj2-all-clickhouse-test-02 :) select * from system.parts where table='.inner.download_daily_mv'\G

SELECT *
FROM system.parts
WHERE table = '.inner.download_daily_mv'

Row 1:
──────
partition: 202008
name: 202008_1_1_0
part_type: Wide
active: 0
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:33:51
remove_time: 2020-08-18 17:38:24
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 1
max_block_number: 1
level: 0
data_version: 1
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_1_1_0/
hash_of_all_files: f4b55a88dac393d25ffe1c703cca4f6d
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: e18b006f608580db132ed90adb46902f

Row 2:
──────
partition: 202008
name: 202008_1_2_1
part_type: Wide
active: 1
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:38:24
remove_time: 0000-00-00 00:00:00
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 1
max_block_number: 2
level: 1
data_version: 1
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_1_2_1/
hash_of_all_files: fdc534a9b9aa0904cde863ee1deff532
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: df972eeaad3304d9a16bfa8cb46861ce

Row 3:
──────
partition: 202008
name: 202008_2_2_0
part_type: Wide
active: 0
marks: 2
rows: 5
bytes_on_disk: 421
data_compressed_bytes: 200
data_uncompressed_bytes: 120
marks_bytes: 192
modification_time: 2020-08-18 17:35:13
remove_time: 2020-08-18 17:38:24
refcount: 1
min_date: 0000-00-00
max_date: 0000-00-00
min_time: 2020-08-18 00:00:00
max_time: 2020-08-22 00:00:00
partition_id: 202008
min_block_number: 2
max_block_number: 2
level: 0
data_version: 2
primary_key_bytes_in_memory: 16
primary_key_bytes_in_memory_allocated: 8192
is_frozen: 0
database: duyalan
table: .inner.download_daily_mv
engine: SummingMergeTree
disk_name: default
path: /data/clickhouse/node2/data/duyalan/%2Einner%2Edownload_daily_mv/202008_2_2_0/
hash_of_all_files: 049d090ea65c24be8544ab86846ea9fa
hash_of_uncompressed_files: 58a4ab29ef36c22884ee7accd528b590
uncompressed_hash_of_compressed_files: 9ffea93e6b9bc375c7f04374b4a4a6a9

3 rows in set. Elapsed: 0.002 sec.

可以看到只有一個active分割槽了202008_1_2_1

所以對於本例中的物化檢視, 在查詢是仍然應該使用聚合函式聚合資料

1
2
3
4
5
6
7
8
9
SELECT 
day,
userid,
sum(downloads),
sum(bytes)
FROM download_no_populate_daily_mv
GROUP BY
day,
userid