[CH] ClickHouse Materialized View
Views
- Normal View
- 資料不會被保存在 disk 中,而是從原本的 table 中直接拿出來
- Parameterized View
- 資料保存方式和 Normal View 一樣,但可以自己定義參數來呈現出不同的資料內容
- Materialized View
- 會建立一個 destination table 來將資料保存在磁碟
- View 中的內容會在 source table 有新資料 Insert 時(UPDATE 或 DELETE 都不會觸發),觸發 MV 中的 SQL,然後寫入 destination table 中。
Regular View
在 ClickHouse 中,一樣可以建立 regular view:
CREATE VIEW uk_terraced_property
AS SELECT *
FROM uk_price_paid
WHERE type = 'terraced';
上面的寫法等同於在 FROM
後面用 subquery:
SELECT count(1)
FROM
(
SELECT *
FROM uk_price_paid
WHERE type = 'terraced'
);
在 ClickHouse 中,一般的 View 並不會保存額外的資料,它只是把 query 保存下來,在每次讀取改 view 時去執行這個 query。
A normal view is nothing more than a saved query.
除非是以下情況比較適合使用一般 view 外,其他的都建議用 Materialized View:
- view 的結果經常會改變
- 即使 view 改變了,它的結果並不會被經常使用
- query 並不是 resource-intensive 的
Parameterized Views
在 ClickHouse 中,可以把參數帶入 Regular View 中,讓這個 view 可以根據不同的參數得到不同的結果。例如:
CREATE VIEW properties_by_town_view
AS SELECT *
FROM uk_price_paid
WHERE town = {town_filter:String};
這時候就多了一個名為 town_name
的參數,可以用來作為 town
這個欄位的 filter:
-- town_name 變成可以帶入的參數
SELECT COUNT()
FROM properties_by_town_view(town_filter = 'LONDON');
Materialized Views (MV)
Using Materialized Views in ClickHouse @ ClickHouse > Blog
基本說明
-
Materialized Views 實際上只是一個 pipeline,把 source table 的資料透過 MV 這個 pipeline 保存在另一張 destination table
- MV 中所寫的 SQL 會在 source table 有新資料被 INSERT 時執行,執行後的結果則會保存到 destination table 中。也就是說,MV 比較像是 source table 到 destination table 的一個 pipeline。
- 每一個 MV 預設背後都會有一個 hidden table 被建立,這個 table 被稱作 destination table。比較好的做法是手動先建立出 destination table ,接著再建立 MV 時,透過
TO
來把 source_table 的資料塞進 destination table
-
當有新的資料寫進 source_table(triggered table)時,同樣的資料會經過特定的 Query 後也寫進 Materialized View
- MV 絕對不會直接去讀原本的 source table
-
因為只有 INSERT 會觸發 MV 更新,所以 如果你的操作會用到 UPDATE 或 DELETE,那 MV 可能不適合你使用,PROJECTION 可能會比較適合
只有 INSERT 會觸發 MV,如果是 DELETE 或 UPDATE 的操作都不會對 MV 產生作用。也就是說,如果會有 UPDATE 或 DELETE 操作的需求,MV 可能不是合適的方法。
-- 先建立 MV
-- 這裡使用 ENGINE = MergeTree 而不是用下面會提到的 TO,因此 ClickHouse 會自動建立一個 hidden table 來保存這個 View 的資料
CREATE MATERIALIZED VIEW uk_prices_by_town
ENGINE = MergeTree
ORDER BY town AS
SELECT
price,
date,
street,
town,
district
FROM uk_price_paid;
-- 接著再把原本已經存在的資料 INSERT 到 MV
INSERT INTO uk_prices_by_town
SELECT
price,
date,
street,
town,
district
FROM uk_price_paid;
uk_price_paid
被稱作 source table 或 triggered table,一旦有新的資料 insert 到uk_price_paid
,則這個新的資料會經過同樣的 querySELECT price, date, ...
這個 query,然後進到uk_prices_by_town
這個 MV。
需要特別留意的是,每次有新 INSERT 的資料時,這些新的資料會經過同樣的 query,但原本就存在 destination table 中的資料則不會被再次拿出來重算,也就是說,如果 SELECT 裡有用到 avg
、max
、等 aggregate function 的話,計算出來的結果並不會考慮原本就存在 destination table 中的資料,而是會完全根據該次 INSERT 的資料所算出的 avg 或 max,多數 的時候這並不會是我們預期的結果。
要解決這個問題的方法會需要使用到下個段落提到的 AggregatingMergeTree。
建立 MV 的 Best Practice
前面提到,只有新 INSERT 到 source table(trigger table)的資料會進到 MV 中,因此如果要建立 MV 又不想讓 source table 還有 MV 中看到的資料不一致,可以跟著下面這三個步驟:
- 定義並建立 destination table
- 在建立 MV 時,使用 TO 來指定 destination table
- 把 source table 中的資料,重新倒進 destination table 中
STEP 1:定義並建立 destination table
CREATE TABLE uk_prices_by_town_dest
(
`price` UInt32,
`date` Date,
`street` LowCardinality(String),
`town` LowCardinality(String),
`district` LowCardinality(String)
)
ENGINE = MergeTree
ORDER BY town;