小編導讀:
本文將重點介紹如何利用物化視圖進行查詢改寫。文章將全面介紹物化視圖的基本原理、關鍵特性、應用案例、使用場景、代碼細節以及主流大數據產品的物化視圖改寫能力對比。
物化視圖在 StarRocks 中扮演著至關重要的角色,它是進行數據建模和加速查詢的神器。特別是在 BI 場景中,通過預先計算 Join 和 Aggregation 操作,物化視圖不僅能大幅度提升查詢性能,還能顯著降低存儲成本。
使用物化視圖通常包含以下三個階段:
-
設計與創建:首先,我們需要仔細分析查詢的特點,以選擇構建最適合的物化視圖。
-
視圖維護:當基礎表的數據發生變化時,物化視圖也需要及時更新,以確保數據的一致性。
-
查詢改寫:利用預計算的數據,物化視圖能有效地加速查詢處理過程,提供更快的響應速度。
本文重點討論物化視圖的第三階段:如何利用物化視圖進行查詢改寫。StarRocks 的異步物化視圖采用了廣泛認可的 SPJG(Select-Projection-Join-Groupby)算法。這允許系統在用戶無需修改任何查詢的前提下,自動將原始查詢轉換為對物化視圖的查詢。借助物化視圖中預計算的結果,這種自動化的查詢改寫大幅降低了計算代價,從而實現了顯著的查詢加速。
在典型的 OLAP 標準測試集中, 通過創建物化視圖可以顯著提升查詢性能:
-
SSB?100GB:與傳統的星形模型相比,物化視圖能將總體查詢耗時減少至原來的 1/3。
-
TPC-H?100GB:這種技術能加速一半的查詢,平均耗時降至原來的 1/5。
基本原理
(StarRocks 物化視圖改寫流程)
物化視圖改寫的關鍵流程如上圖所示,整體上可以劃分為以下三個階段:
-
預處理:在 Analyzer 處理后得到的邏輯計劃樹的基礎上,系統會識別出相關的物化視圖候選集。此階段還包括過濾掉無法用于改寫的物化視圖,以縮小搜索空間并確保物化視圖數據的新鮮度。
-
SPJG?物化視圖改寫:應用基于cost-based的 SPJG 物化視圖改寫規則,自動遍歷搜索空間中可用于改寫的子樹,并嘗試進行改寫,并且最終會根據 Cost Model 選擇最優的改寫方案。
-
后處理:對物化視圖改寫后的 Plan 執行列裁剪、謂詞下推、分區裁剪等優化操作,以提升改寫后 Plan 的執行性能。
舉例說明,對于一條具體的查詢,物化視圖的改寫可以分為以下幾個步驟處理:
-
預處理:
-
分析訪問表:首先分析查詢涉及的基表, 根據這些基表與物化視圖之間的依賴關系,識別可能有用的物化視圖
-
候選視圖篩選:對于復雜的查詢可能存在大量的候選物化視圖,直接考慮所有視圖會導致計算開銷過大。因此,因此,需要根據視圖的“適用性”對候選視圖進行排序,并選擇一個子集進行進一步分析
-
新鮮度驗證:檢查候選物化視圖的數據新鮮度,若物化視圖數據不滿足查詢的要求,則拋棄這些視圖
-
TEXT 改寫
-
當查詢與某個物化視圖在 AST tree/語法樹結構上完全一致時,可以通過文本匹配直接將查詢改寫為對該視圖的訪問
-
SPJG 查詢改寫
-
SPJG 改寫適用于查詢與物化視圖有所差異的場景,可以對物化視圖進行補償改寫,提供了更大的靈活性,但實現上也更為復雜
-
在 SQL 優化器中應用多種規則來匹配視圖和查詢,對所有可能的 Query Plan 進行改寫,這一過程的計算開銷相對較高
-
改寫后,對于產生的所有可能的改寫結果,使用 Cost Model 來評估并選擇最優的改寫方案
-
后置處理
-
對改寫后的查詢計劃應用更多優化器規則,如列裁剪、謂詞下推和分區裁剪等
select sum(lo_revenue) as lo_revenue, d_year, p_brand
from lineorder
join dates on lo_orderdate = d_datekey
join part on lo_partkey = p_partkey
join supplier on lo_suppkey = s_suppkey
where p_category = 'MFGR#12' and s_region = 'AMERICA'
group by d_year, p_brand
order by d_year, p_brand;
關鍵特性
除核心能力之外,StarRocks 物化視圖自動改寫還包括以下關鍵特性:
-
數據一致性:
-
內部表一致性:確保物化視圖改寫的結果與查詢原始表結果完全一致,實現強數據一致性。
-
過期數據處理:支持配置數據過期容忍時間,適應數據頻繁變更的場景,通過 staleness 改寫技術應對數據變化。
-
復雜查詢支持
-
多表?Join?支持:支持各種類型的 join,包括 view delta join 和 join derivability rewrite 等復雜 join 場景的改寫,優化大寬表查詢。
-
聚合查詢加速:通過聚合改寫技術加速聚合查詢,提升報表查詢性能。
-
嵌套視圖改寫:支持嵌套物化視圖改寫,解決復雜查詢的改寫問題,擴展改寫范圍。
-
復雜表達式支持:夠處理包括函數調用和四則運算在內的復雜表達式,滿足復雜的分析計算需求
-
實時數據融合
-
新鮮數據查詢加速:利用 union 改寫和 TTL 功能聯合使用,加速新鮮數據查詢,并實現歷史數據自動回查原表。
-
多數據源支持
-
邏輯視圖物化:允許在邏輯視圖上創建物化視圖,支持基于 view 建模的場景下的查詢加速
-
外部表物化視圖:支持包括 Hive、Iceberg、Hudi、DeltaLake、Paimon、JDBC(MySQL Dialet)等,提升數據湖場景下的查詢性能
應用案例
在攜程的 BI 平臺中,通過 StarRocks MV 實現了幾方面的性能加速:
-
Projection MV:在 Hive Table 基礎上創建 Projection MV,發揮 StarRocks 內表和存儲引擎的性能,使得關鍵報表能夠獲得幾倍的性能加速。由于 StarRocks MV 能夠支持 Hive Table 的自動刷新,一次創建后幾乎不需要后續的維護,從而大幅降低 ETL 的維護成本
-
嵌套視圖:在 MV 的基礎上,對復雜查詢創建嵌套視圖,以進一步加速關鍵報表查詢。這些查詢通常涉及 BI 場景中的典型操作,如 Join、Aggregation、多層聚合等復雜查詢。
-
AutoMV:利用 AutoMV 能力,分析慢查詢自動推薦出合適的物化視圖,從而進一步減少了人工維護成本。
使用場景
Join Rewrite
StarRocks 支持 join 查詢改寫,支持的 join 類型包括:Inner join/cross join/left outer join/full outer join/right outer join/semi join/anti join。
下面是一個 join mv 改寫的例子,建表如下:
CREATE?TABLE?`customer`?(`c_custkey`?int(11)?NOT?NULL?COMMENT?"",`c_name`?varchar(26)?NOT?NULL?COMMENT?"",`c_address`?varchar(41)?NOT?NULL?COMMENT?"",`c_city`?varchar(11)?NOT?NULL?COMMENT?"",`c_nation`?varchar(16)?NOT?NULL?COMMENT?"",`c_region`?varchar(13)?NOT?NULL?COMMENT?"",`c_phone`?varchar(16)?NOT?NULL?COMMENT?"",`c_mktsegment`?varchar(11)?NOT?NULL?COMMENT?""
)?ENGINE=OLAP
DUPLICATE?KEY(`c_custkey`)
COMMENT?"OLAP"
DISTRIBUTED?BY?HASH(`c_custkey`)?BUCKETS?12
PROPERTIES?(
"replication_num"?=?"1"
);CREATE?TABLE?`lineorder`?(`lo_orderkey`?int(11)?NOT?NULL?COMMENT?"",`lo_linenumber`?int(11)?NOT?NULL?COMMENT?"",`lo_custkey`?int(11)?NOT?NULL?COMMENT?"",`lo_partkey`?int(11)?NOT?NULL?COMMENT?"",`lo_suppkey`?int(11)?NOT?NULL?COMMENT?"",`lo_orderdate`?int(11)?NOT?NULL?COMMENT?"",`lo_orderpriority`?varchar(16)?NOT?NULL?COMMENT?"",`lo_shippriority`?int(11)?NOT?NULL?COMMENT?"",`lo_quantity`?int(11)?NOT?NULL?COMMENT?"",`lo_extendedprice`?int(11)?NOT?NULL?COMMENT?"",`lo_ordtotalprice`?int(11)?NOT?NULL?COMMENT?"",`lo_discount`?int(11)?NOT?NULL?COMMENT?"",`lo_revenue`?int(11)?NOT?NULL?COMMENT?"",`lo_supplycost`?int(11)?NOT?NULL?COMMENT?"",`lo_tax`?int(11)?NOT?NULL?COMMENT?"",`lo_commitdate`?int(11)?NOT?NULL?COMMENT?"",`lo_shipmode`?varchar(11)?NOT?NULL?COMMENT?""
)?ENGINE=OLAP
DUPLICATE?KEY(`lo_orderkey`)
COMMENT?"OLAP"
DISTRIBUTED?BY?HASH(`lo_orderkey`)?BUCKETS?48
PROPERTIES?(
"replication_num"?=?"1"
);
基于上述的表,構建物化視圖
-- MV
create materialized view join_mv1
distributed by hash(`lo_orderkey`)
as
select lo_orderkey, lo_linenumber, lo_revenue, lo_partkey, c_name, c_address
from lineorder inner join customer
on lo_custkey = c_custkey;
則如下的查詢可以被改寫為查詢join_mv1:
--?Query
select?lo_orderkey,?lo_linenumber,?lo_revenue,?c_name,?c_address
from?lineorder?inner?join?customer
on?lo_custkey?=?c_custkey;
在 join 中,select 中支持復雜表達式改寫,比如四則運算,string 函數,日期函數處理,case when 表達式,or 謂詞等等。
select?lo_orderkey,?lo_linenumber,?(2?*?lo_revenue?+?1)?*?lo_linenumber,?upper(c_name),?substr(c_address,?3)
from?lineorder?inner?join?customer
on?lo_custkey?=?c_custkey;
上述的 join 改寫場景是 Query 的 join 類型和表集合同 MV 相同的場景,StarRocks 中還擴展支持了以下幾種 join 場景的改寫。
1.1 Query delta join rewrite
query delta join 就是指查詢的 join 表是物化視圖中 join 表的超集場景。比如如下 query 是 lineorder/customer/part三表 join,join_mv1 只有 lineorder/customer 兩表 join,StarRocks 支持將 query 改寫到 join_mv1。
select?lo_orderkey,?lo_linenumber,?lo_revenue,?c_name,?c_address,?p_name
fromlineorder?inner?join?customer?on?lo_custkey?=?c_custkeyinner?join?part?on?lo_partkey?=?p_partkey?
改寫之后的 plan 如下:
1.2 View delta join rewrite
View delta join 指的是在查詢中,涉及的 join 表是物化視圖中 join 表的子集。這種場景的改寫能力通常適用于大寬表查詢。例如,在 SSB 場景中,可以構建一個包含所有表的物化視圖,將多個表 join 成一個大寬表。這樣,所有 SSB 查詢都可以通過物化視圖的透明改寫來提升查詢性能。測試結果表明,通過物化視圖改寫后的多表 join 查詢,其性能可達到直接查詢大寬表的水平。
為了實現 view delta join 的改寫,要求物化視圖中的 join 必須與查詢中的 join 具有1:1的 cardinality preservation(基數保持)關系。以下是 SSB 改寫的示例。在滿足下列的 join 條件時,都可以進行 cardinality preservation join 改寫。任何滿足其中一種條件的 join,都能夠進行 view delta join 的改寫。
在 StarRocks 中, 可以用以下語法指定主外鍵關系:
CREATE?TABLE?`customer`?(...)?
PROPERTIES?("unique_constraints"?=?"c_custkey"?#指定唯一鍵
);CREATE?TABLE?`lineorder`?(...)?
PROPERTIES?("foreign_key_constraints"?=?"(lo_custkey)?REFERENCES?customer(c_custkey);(lo_partkey)?REFERENCES?part(p_partkey);(lo_suppkey)?REFERENCES?supplier(s_suppkey)"?#指定外鍵約束
);
對于 SSB 中的 Query,往往不會查詢 MV 的所有表,但是通過指定了主外鍵關系,仍然能夠利用物化視圖改寫加速,以其中一個查詢為例:
--MV
CREATE?MATERIALIZED?VIEW?lineorder_flat_mv
DISTRIBUTED?BY?HASH(LO_ORDERDATE,?LO_ORDERKEY)?BUCKETS?48
partition?by?LO_ORDERDATE
REFRESH?manual
PROPERTIES?("replication_num"?=?"1"
)
AS?SELECT*FROM?lineorder?AS?lINNER?JOIN?customer?AS?c?ON?c.C_CUSTKEY?=?l.LO_CUSTKEYINNER?JOIN?supplier?AS?s?ON?s.S_SUPPKEY?=?l.LO_SUPPKEYINNER?JOIN?part?AS?p?ON?p.P_PARTKEY?=?l.LO_PARTKEYINNER?JOIN?dates?AS?d?ON?l.LO_ORDERDATE?=?d.D_DATEKEY;????
--?Query
select?sum(lo_revenue)?as?lo_revenue,?d_year,?p_brand
from?lineorder
join?dates?on?lo_orderdate?=?d_datekey
join?part?on?lo_partkey?=?p_partkey
join?supplier?on?lo_suppkey?=?s_suppkey
where?p_category?=?'MFGR#12'?and?s_region?=?'AMERICA'
group?by?d_year,?p_brand
order?by?d_year,?p_brand;
1.3 Join derivability rewrite
Join 派生改寫是在物化視圖(MV)的 JOIN 類型與查詢(query)不一致,但 MV 的結果包含查詢的結果時,進行的改寫,例如 MV 使用了 OUTER JOIN,而查詢是 INNER JOIN。目前分為以下兩種情況:
-
兩表?join?的情況:此時會枚舉所有 JOIN 順序和多種 JOIN 方式,檢查 INNER/SEMI/ANTI/OUTER 之間是否兼容,在兼容的情況下仍然能夠進行改寫
-
三表或三表以上的?join:多表時無法枚舉所有可能性,因此只做相對嚴格的兼容性檢查
舉個例子,MV 使用了 OUTER JOIN,而 Query 是 INNER JOIN, 直接改寫會導致結果中包含 NULL,此時 StarRocks 會補償一個謂詞?IS NOT NULL
?去保證結果的正確性。
--?MV
create?materialized?view?join_mv3?
distributed?by?hash(`lo_orderkey`)
as
select?lo_orderkey,?lo_linenumber,?c_name,?sum(lo_revenue)?as?total_revenue,?max(lo_discount)?as?max_discount?
from?lineorder?
left?join?customer
on?lo_custkey?=?c_custkey
group?by?lo_orderkey,?lo_linenumber,?c_name;--?Query
select?lo_orderkey,?lo_linenumber,?c_name,?sum(lo_revenue)?as?total_revenue,?max(lo_discount)?as?max_discount?
from?lineorder?
join?customer
on?lo_custkey?=?c_custkey
group?by?lo_orderkey,?lo_linenumber,?c_name;
Aggregation Rewrite
支持多表聚合查詢的改寫,并且支持所有的聚合函數,其中包括 bitmap_union/hll_union/percentile_union 等。
--?MV
create?materialized?view?agg_mv1
distributed?by?hash(`lo_orderkey`)
as
select?lo_orderkey,?lo_linenumber,?c_name,?sum(lo_revenue)?as?total_revenue,?max(lo_discount)?as?max_discount?
from?lineorder?inner?join?customer
on?lo_custkey?=?c_custkey
group?by?lo_orderkey,?lo_linenumber,?c_name;
如下查詢可被 agg_mv1 改寫
--?Query
select?lo_orderkey,?lo_linenumber,?c_name,?sum(lo_revenue)?as?total_revenue,?max(lo_discount)?as?max_discount?
from?lineorder?inner?join?customer
on?lo_custkey?=?c_custkey
group?by?lo_orderkey,?lo_linenumber,?c_name;
除此最基礎的場景之外, 還有一些擴展的場景。
2.1 Rollup
同時,支持聚合物化視圖的上卷改寫,例如當查詢中的 GROUP BY 比 MV 的 GROUP BY 更少時,能夠一定程度上服用 MV 的結果,但是仍然需要做二次聚合,才能得到最終結果:
2.2 Count distinct
Count distinct 計算一般應用于精確去重的場景。與普通的聚合不同在于,它通常無法利用上卷進行改寫。
但是 StarRocks 支持通過 bitmap 來實現 count distinct 改寫,進而實現基于物化視圖的高性能精確去重:
-
創建物化視圖時,使用?
bitmap_union(to_bitmap(lo_custkey))
-
查詢時,仍然使用普通的?
count(distinct lo_custkey)
?即可
--?MV
create?materialized?view?distinct_mv
distributed?by?hash(`lo_orderkey`)
as
select?lo_orderkey,?bitmap_union(to_bitmap(lo_custkey))?as?distinct_customer
from?lineorder
group?by?lo_orderkey;--?Query
select?lo_orderkey,?count(distinct?lo_custkey)?from?lineorder?group?by?lo_orderkey;
Nested mv rewrite
StarRocks 支持嵌套物化視圖改寫。比如有如下三個物化視圖,agg_mv2 是基于物化視圖 join_mv2 之上構建的物化視圖;agg_mv3 是基于 agg_mv2 之上構建的物化視圖。通過這種方式,能夠有效處理復雜的多層子查詢。
Union rewrite
Union Rewrite 指的是物化視圖的數據是查詢的子集,仍然能夠進行改寫:
-
Partial Predicate:MV 謂詞范圍是查詢的子集,此時會將差集回原表查詢,再 UNION 起來
-
Partial Partition:MV 只物化了部分 Partition,此時其余的 Partition 回原表查詢
4.1 Partial predicate
例如 MV 中有謂詞?where lo_orderkey < 300000000
--?MV
create?materialized?view?agg_mv4
distributed?by?hash(`lo_orderkey`)
as
select?lo_orderkey,?sum(lo_revenue)?as?total_revenue,?max(lo_discount)?as?max_discount?
from?lineorder
where?lo_orderkey?<?300000000
group?by?lo_orderkey;
則如下的查詢會被改寫:其中,agg_mv5 包含?lo_orderkey < 300000000?的數據,lo_orderkey >= 300000000?的數據通過直接讀取lineorder表進行計算,最終通過union之后再聚合,獲取最終結果。
--?Query
select?lo_orderkey,?sum(lo_revenue)?as?total_revenue,?max(lo_discount)?as?max_discount?
from?lineorder
group?by?lo_orderkey;
4.2 Partial partition
針對分區表來說,如果基于分區表構建分區物化視圖,查詢的分區范圍是物化視圖的最新分區范圍的超集,查詢會被 union 改寫。
比如,有如下的物化視圖, base 表?lineorder?的目前包含 p1-p7 分區,物化視圖目前也包括 p1-p7 分區。
--?MV
create?materialized?view?agg_mv5
distributed?by?hash(`lo_orderkey`)
partition?by?range(`lo_orderdate`)
refresh?manual
as
select?lo_orderdate,?lo_orderkey,?sum(lo_revenue)?as?total_revenue,?max(lo_discount)?as?max_discount?
from?lineorder
group?by?lo_orderkey;
如果?lineorder?新增一個 p8 分區,分區范圍是[("19990101"), ("20000101")),則下面的查詢會被改寫為 union:
--?Query
select?lo_orderdate,?lo_orderkey,?sum(lo_revenue)?as?total_revenue,?max(lo_discount)?as?max_discount?
from?lineorder
group?by?lo_orderkey;
其中,agg_mv5 包含 p1-p7 分區的數據,p8 分區的數據通過直接讀取 lineorder 表進行計算,最終通過 union 之后再聚合,獲取最終結果。
MV on views
支持從 view 上創建 MV,并且查詢 view 的時候能夠實現透明改寫。在查詢改寫時會有兩種方式:
-
VIEW 展開:內聯整個 VIEW,當做普通的 QUERY 改寫
-
VIEW 獨立:將 VIEW 作為獨立的算子,不考慮內容,再進行改寫
比如有如下的 view
--?View
create?view?customer_view1?as
select?c_custkey,?c_name,?c_address
from?customer;--?View
create?view?lineorder_view1?as
select?lo_orderkey,?lo_linenumber,?lo_custkey,?lo_revenue
from?lineorder;
則可以構建如下的物化視圖,在改寫的時候,MV 上的 view 會被自動展開到 view 引用的 base 表上,然后進行透明匹配改寫。
--?MV
create?materialized?view?join_mv1
distributed?by?hash(`lo_orderkey`)
as
select?lo_orderkey,?lo_linenumber,?lo_revenue,?c_name
from?lineorder_view1?inner?join?customer_view1
on?lo_custkey?=?c_custkey;
MV on External catalog
StarRocks 支持在 Hive/Hudi/Iceberg/Paimon/DeltaLake/JDBC 外表上構建物化視圖,并且能夠進行透明改寫。上述所有的改寫能力大部分在外表物化視圖中都支持,具體支持程度可參考使用文檔。
StarRocks 在物化視圖改寫上,目前還有如下限制:
-
不支持非確定性函數的改寫,包括RAND/RANDOM/UUID/SLEEP等
-
在SPJG改寫模式下,不支持窗口分析函數的改寫;基于文本的改寫,不受這個限制
-
在SPJG改寫模式下,如果mv定義語句中包含limit/order by/union/except/intersect/minus/grouping sets/with cube/with rollup,則無法用于改寫;基于文本的改寫,不受這個限制
-
部分外表(Hudi/DeltaLake)上還不支持查詢結果的強一致
能力對比
以下列出主流大數據產品在物化視圖上的改寫能力:
總結
本文主要介紹了 StarRocks 中物化視圖查詢改寫的技術原理,從優化器的執行流程,到對不同查詢的處理 Join、Aggregation、View、Union 等,以及內部視角的反省和外部視角的對比。希望本文能夠對關心技術原理的讀者有所幫助,對 StarRocks 的用戶帶來更多的技術洞察和業務啟發
參考文獻:
-
Optimizing Queries Using Materialized Views: A Practical, Scalable Solution
-
Materialized view in Apache calcite:?https://calcite.apache.org/docs/materialized_views.html
-
Oracle:https://docs.oracle.com/en/database/oracle/oracle-database/12.2/dwhsg/advanced-query-rewrite-materialized-views.html#GUID-0906CA6B-7EE3-42E1-A598-C6541BCD9B36
延伸閱讀:
StarRocks 物化視圖:指標平臺性能提升的新引擎
重新定義物化視圖,你必須擁有的極速湖倉神器!
QPS 提升 10 倍!滴滴借助 StarRocks 物化視圖實現低成本精確去重
StarRocks 技術內幕 | 多表物化視圖的設計與實現
更多交流,聯系我們:StarRocks