作者:阿里雲數據庫OLAP產品部 雲曦
預計算和緩存是計算機領域提高性能以及降低成本的最常見的手段之一。對於那些經常重復的請求,如果可以通過緩存回答,比重新計算結果或從速度較慢的數據存儲中讀取要快得多,消耗更少的系統資源。在數據庫領域中,物化視圖是預計算和緩存的自然體現。
本文主要介紹什么是物化視圖,以及如何實現基於物化視圖的查詢改寫。
在第一部分,我們會簡單介紹物化視圖,並介紹基於物化視圖的查詢改寫的用途。在第二部分,我們將介紹查詢優化器使用物化視圖進行查詢改寫的匹配和改寫過程。最后,我們將介紹查詢改寫的幾種實現方式,及其優缺點。
背景介紹
物化視圖
物化視圖是將查詢結果預先計算並存儲的一張特殊的表。"物化"(Materialized) 這個詞是相對於普通視圖而言。普通視圖較普通的表提供了易用性和靈活性,但無法加快數據訪問的速度。物化視圖像是視圖的緩存,它不是在運行時構建和計算數據集,而是在創建的時候預先計算、存儲和優化數據訪問,並自動刷新來保證數據的實時性。
對於數據倉庫,物化視圖最重要的功能就是查詢加速。數據倉庫中存在大量在大型表上執行復雜的查詢,這些查詢會消耗大量資源和時間。物化視圖可以通過預計算的結果回答查詢,消除昂貴的聯接和聚合所帶來的開銷,大幅度改善查詢處理時間,降低系統負載。對於可以預見並反復使用相同子查詢結果的查詢,物化視圖特別有用。
為了實現物化視圖的潛力,需要解決三個問題:
- 物化視圖選擇:選擇哪些查詢和表構建物化視圖;
- 物化視圖維護:減少物化視圖更新成本和時間;
- 物化視圖運用:如何使用物化視圖加速查詢。
本文主要從查詢優化器的角度,介紹使用物化視圖加速查詢背后的技術實現。
基於物化視圖的查詢改寫
直接查詢物化視圖可以大幅度改善查詢處理時間,但是需要用戶修改查詢語句。使用物化視圖加速查詢的一個重要問題是,如何采用一種系統化和自動化的方法,自動使用物化視圖回答查詢。通過這種透明改寫,物化視圖可以像索引一樣添加或刪除,而不會影響已有 SQL。
查詢改寫使得物化視圖具有廣泛的用途:
- 物化視圖可以透明地改寫查詢,無需改造業務就能使用物化加速查詢;
- 方便地應用緩存公共結果集,以及預計算等跨查詢優化手段;
- 對於數據倉庫,數據集成場景,物化視圖可以物化外表結果,屏蔽多個數據源的差異,實現本地副本或讀寫分離;
- 查詢改寫結合自動構建物化視圖,實現數據庫自治加速。
查詢改寫的問題定義
為了實現更大范圍的改寫,查詢改寫通常被集成在優化器規則中。這有幾個方面的好處。
首先查詢改寫可以利用優化器其他規則。依靠優化器其他規則將查詢轉換成標准和統一的形式,簡化匹配流程,增加改寫范圍。其中比較重要的規則是列消除,謂詞下推,解關聯子查詢等。解關聯子查詢規則允許物化視圖對包含關聯子查詢的查詢進行改寫加速。
其次,優化器可以遞歸每一個子樹能否被某個視圖進行改寫。每個相關視圖都會對每個子樹產生多次改寫,一個查詢語句不同部分可能被不同的視圖改寫。最終所有的改寫都進入基於成本的選擇器中,與原始查詢一起選出最優的查詢計划。
查詢改寫算法只需要考慮給定的查詢表達式和視圖,判斷這個查詢表達式能否從視圖中計算出來,然后從視圖上構造一個等價的補償表達式,與原查詢表達式等價。查詢改寫的范圍應該盡可能大,查詢改寫的目標是使用少量物化視圖改寫大量查詢。最終由優化器選擇出一個最優的查詢計划。
查詢改寫檢查
優化器通過多種方式來改寫查詢。最簡單的一種情況是物化視圖的查詢與查詢完全匹配,符合這種查詢重寫類型的查詢數量很少。為了進行更通用的匹配,優化器會嘗試使用各種規則構造一個等價表達式改寫查詢。
查詢改寫檢查包含兩個步驟,改寫匹配檢查和構建等價表達式。一個查詢能被視圖回答需要滿足下面兩個條件:
- 物化視圖 Join 關系在查詢中存在
- 物化視圖有足夠的數據來回答查詢
部分條件下即使不滿足條件,視圖也可能會被用於改寫。例如視圖包含一部分查詢所需要的數據,可以使用物化視圖回答部分查詢,剩下的數據從原始數據中計算。這部分改寫檢查會放在高級改寫規則中進行介紹。
具體來說改寫檢查會依次進行 Join 檢查和 Ouput 檢查,如果查詢或視圖中含有 Grouping By 和 Aggregation,還會進行額外的檢查,如果需要,會嘗試對視圖進一步聚合。
Join 檢查
當查詢和視圖的表的 Join 關系相同時,視圖才可能包含查詢需要的所有的行和列。一種簡單的方式是只考慮沒有子查詢的 Inner Join,這時只用比較查詢表和數量是否完全一致即可。或者通過一系列規則檢查查詢和視圖的關系代數樹包含的 Join 關系是等價的。更通用的一種方法是構建 Join Graph。Join Grpah 是一個以關系為結點,聯接為邊的圖。Inner Join 的條件表示為無向邊,Outer Join 的條件表示為有向邊。Join Graph 由查詢關系代數樹構建而來,通過比較 Join Graph 就可以檢查物化視圖和查詢包含相同的 Join 關系。
例如對於下面的查詢
select c_custkey, c_name, l_orderkey, l_partkey, l_quantity from ( select l_orderkey,l_partkey, l_quantity from lineitem where l_orderkey < 1000 and l_shipdate = l_commitdate ) subquery join orders on subquery.l_orderkey = o_orderkey left join customer on o_custkey = c_custkey
對於物化視圖
create materialized view mview1 enable query rewrite as select * from lineitem join customer On subquery.l_orderkey = o_orderkey left outer join orders On o_custkey = c_custkey
查詢和物化視圖具有相同的 Join 關系,查詢可以被改寫
select c_custkey, c_name, l_orderkey,l_partkey, l_quantity from mview1 where l_orderkey < 1000 and l_shipdate = l_commitdate
Output 檢查
Ouput 檢查保證物化視圖有足夠的數據回答查詢,這包括 3 個步驟:
- 驗證查詢所有輸出需要的列能夠從視圖中計算出來
- 優化器需要檢查視圖包含查詢所有需要的行數,也就是視圖的謂詞的范圍大於查詢的范圍
- 補償謂詞能否從視圖中計算得來
等價關系
查詢改寫能夠通過查詢中的等價關系擴展改寫的范圍。因為等值條件具有傳遞性,等價關系可以從等值條件或者代數關系推導而來。例如可以從 A=date_format(now(), '%Y%m%D') 和 B=date_format(now(), '%Y%m%D'),因為函數是確定性的,可以得到 A=B,如果我們還有 inner join 的條件 B=C,可以進一步得到 A=B=C。
可能會有某些其他的條件可以推導出等價關系,例如可以從 A=2,B=3,C=5 推導出 C = A + B。相比等值關系,尋找這種關系會使搜索過程更加復雜,而且我們無法實現所有的可能的相等關系,因此很少考慮這樣的表達式。等價關系推導只搜索等值條件,即使可能錯過一些改寫機會,這樣的淺層搜索可以保證速度。
表達式檢查
為了確保查詢輸出的列和補償謂詞需要的列都能從視圖中計算出來,我們需要一種方法確定來自查詢的表達式是否和視圖中的表達式相等,或者能從中計算出來。表達式檢查無法純粹從語法上實現,兩個表達式或者符號的文本相同,並不說明他們關系相等。例如別名的存在就會破壞基於語法的檢查。
表達式檢查需要通過等價關系和表的對應關系推導而來。如果視圖和查詢中所有的表都是唯一的,那么來自同一個表的列是等價的;如果視圖和查詢中有表出現多次,即 self join,那么查詢到視圖的表的映射就存在多種可能,每種可能都需要進行一次改寫嘗試。有了視圖和查詢之間列的對應關系和上一節的等價關系,通過代數系統確定來自查詢的表達式是否和視圖中的表達式相等,或者能否從中計算出來。
存在一些啟發式的規則,允許一個表達式從另外的表達式中計算出來。比如算數規則從 x + 1 中計算出 x,例從 SUM(x) 和 COUNT(x) 計算出 AVG(x),還存在一些 Function Dependency 規則,例如時間函數,可以通過返回的天的結果計算年等。
謂詞檢查
視圖改寫需要物化視圖中存在查詢所有需要的行數,即視圖的謂詞的范圍大於或等於查詢的范圍。使用 Wq 表示查詢的謂詞,Wv表示視圖的謂詞,我們需要檢查 Wq => Wv,其中 => 表示 Wq 滿足 Wv 的含義。
優化器提取所有的謂詞,並將他們轉換為 CNF 的形式,即 W=P1 ^ P2 ^ ... ^ Pn。將謂詞按照等值謂詞,范圍謂詞和剩余謂詞進一步分為 W= PE ^ PR ^ PU。PE,PR,PU 分別代表等值謂詞,范圍謂詞和剩余謂詞。謂詞檢查變成了 PEq ^ PRq ^ PUq => PEv ^ PRv ^ PUv,由於正交的性質,最終將問題分解成 PEq => PEv,PEq ^ PRq => PRv,PEq ^ PUq => PUv 三類檢查。
查詢的等值謂詞用於推導等價關系。查詢中任何視圖不滿足等價關系的謂詞都構成補償謂詞,視圖中存在查詢無法滿足的等價關系則無法改寫。
范圍謂詞在優化器中可以存儲為范圍的形式,可以方便的計算差集。對於查詢中每一個范圍謂詞,如果視圖存在對應的范圍精准匹配,不需要進行補償,否則視圖范圍必須大於查詢的范圍,並通過差集構造一個補償謂詞。
查詢表達式和試圖剩余的謂詞共同構成剩余謂詞PE,只能進行精准匹配。查詢中任何與視圖不匹配的謂詞都構成補償謂詞。查詢與視圖不匹配則無法改寫。
所有的補償謂詞都需要通過表達式檢查,確保可以從視圖的輸出中計算出來。
例如查詢
select l_orderkey, o_custkey, l_partkey,l_quantity*l_extendedprice from lineitem, orders, part where l_orderkey = o_orderkey and l_partkey = p_partkey and l_partkey >= 150 and l_partkey <= 160 and o_custkey = 123 and o_orderdate = l_shipdate and p_name like ‘%abc%’ and l_quantity*l_extendedprice > 100
物化視圖
create materialized view mview2 Enable Query Rewrite as select l_orderkey, o_custkey, l_partkey,l_shipdate, o_orderdate,l_quantity*l_extendedprice as gross_revenue from lineitem, orders, part where l_orderkey = o_orderkey and l_partkey = p_partkey and p_partkey >= 150 and o_custkey >= 50 and o_custkey <= 500 and p_name like ‘%abc%’
查詢可以被改寫為
select l_orderkey, o_custkey, l_partkey, gross_revenue from mview2 where l_partkey <= 160 and o_custkey = 123 and o_orderdate = l_shipdate and gross_revenue > 100
Grouping 和 Aggregation 檢查
如果視圖和查詢帶有 GroupBy 或 Aggregation 函數,需要進行額外檢查:
- 檢查查詢請求的數據分組是否與物化視圖中存儲的數據分組相同,如果不同,優化器會嘗試對物化視圖進行匯總。
- 如果需要進一步匯總計算,所有需要的列的都可以從視圖輸出中計算出來。
例如查詢
select c_nationkey, sum(l_quantity*l_extendedprice) from lineitem, orders, customer where l_orderkey = o_orderkey and o_custkey = c_custkey group by c_nationkey
物化視圖
create materialized view mview3 enable Query rewrite as select o_custkey, count_big(*) as cnt, sum(l_quantity*l_extendedprice) as revenue from lineitem, orders where l_orderkey = o_orderkey group by o_custkey
查詢可以被改寫為
select c_nationkey, sum(revenue) from customer join mview3 on c_custkey = o_custkey group by c_nationkey
如果分組列表不同,只能改寫 Group By 在查詢最外部的情況,否則無法進行進一步聚合,無法滿足第二個改寫要求。只有特定的聚合函數支持進一步聚合,常見的聚合函數有 MIN,MAX,SUM,COUNT。
例如如下查詢
select o_custkey, count(*) as cnt, sum(l_quantity*l_extendedprice) as revenue from lineitem, orders where l_orderkey = o_orderkey group by o_custkey
物化視圖
create materialized view mview4 enable Query rewrite as select o_custkey, l_partkey, count(*) as cnt, sum(l_quantity*l_extendedprice) as revenue from lineitem, orders where l_orderkey = o_orderkey group by o_custkey, l_partkey
可以被改寫為
select c_nationkey, sum(cnt) as cnt, sum(revenue) as revenue from mview4 where c_custkey = o_custkey group by o_custkey
如果聚合函數在視圖中不存在,可以通過一些規則進行計算,例如從 SUM(x) 和 COUNT(x) 計算出 AVG(x),從 SUM(x) + SUM(y) 中計算出 SUM(x + y) ,這些依賴表達式改寫檢查中啟發式的規則。
高級改寫規則
如果物化視圖和查詢不滿足上一節的改寫規則,還可以通過其他規則進行轉換。
Join 補償
如果只考慮 Inner Join,視圖和查詢的 Join 關系 不一致有兩種情況:查詢比視圖包含更多的聯接,或者視圖包含更多的聯接。
如果查詢比視圖包含更多的表,我們可以簡單把缺少的 Join 添加在視圖之上,使其滿足改寫要求。Join 條件會通過謂詞改寫正確的添加進來,優化器也可以通過后續規則調整計划。
Join 補償使優化器可以更早的進行匹配改寫,減少改寫的嘗試次數。
例如如下查詢
select c_custkey, c_name, l_orderkey,l_partkey, l_quantity From lineitem, customer, orders Where l_orderkey = o_orderkey And o_custkey = c_custkey Where l_orderkey between 1000 and 1500 And l_shipdate = l_commitdate
物化視圖
Create materialized view mview5 Enable query rewrite as Select l_orderkey,l_partkey, l_quantity From lineitem, orders Where l_orderkey = o_orderkey And o_orderkey >= 500
可以被改寫為
Select l_orderkey, l_partkey, l_quantity From mview5 join customer on o_custkey = c_custkey Where l_orderkey between 1000 and 1500 And l_shipdate = l_commitdate
Join 消除
如果一個 Join 出現在視圖中但是沒有出現在查詢中,可以嘗試使用 Join 消除規則。Join 消除是優化器優化中的一項常見方法,如果 Join 滿足以下5個條件,則表 T1 在與表 T2 的聯接中時不變的:
- 聯接條件是一個簡單的等值條件
- T1.fk 是 T2.pk 的外鍵
- T1.fk 滿足非 null 約束
- T2 沒有任何的謂詞
- T2 在與 T1 以外的表的聯接是不變的
這意味着表 T1 在與 T2 的 Join 關系不會影響 T1 的結果,視圖中的 T2 可以在 Join Graph 中忽略。不變聯接的存在允許在基礎物化視圖上創建更大的並集或超集,從而允許物化視圖包含更大的預計算,也可以改寫更多的查詢。
例如查詢
Select l_orderkey, l_partkey, l_quantity From lineitem Where l_orderkey between 1000 and 1500 And l_shipdate = l_commitdate
物化視圖
Create materialized view mview6 Enable query rewrite as Select c_custkey, c_name, l_orderkey,l_partkey, l_quantity From lineitem, orders, customer Where l_orderkey = o_orderkey And o_custkey = c_custkey And o_orderkey >= 500
可以被改寫成
select l_orderkey, l_partkey, l_quantity from mview6 where l_orderkey between 1000 and 1500 and l_shipdate = l_commitdate
Join 派生
如果存在 Outer Join,視圖和查詢 Join 關系不一致,可以嘗試利用 Join 派生性,從物化視圖中的聯接重新計算查詢中的聯接。例如,能從物化視圖中的 left Outer Join 的結果里,計算 Inner Join,Anti Join 的結果,Inner Join 又能進一步計算 Semi Join,這樣就能使用物化視圖過濾某些行來回答不同具有不同 Join 關系的查詢。
Join 派生可以擴展改寫的范圍,允許優化器將基於物化視圖的改寫與解關聯的規則規則結合,改寫帶有 IN,EXISTS 等的查詢,也可以避免其他優化規則對 Join 的調整,例如 EliminateOuterJoin 和 PredicatePushDown 可能會將 outer join 優化成 inner join。
Union 改寫
物化視圖只包含一部分查詢所需的數據,也可以用於查詢改寫。在很多場景下,物化視圖不會也無法存儲全部的數據。
一個典型的情況是,數據在不斷的寫入,但是寫入只發生在最近一段時間內。在持續刷新的表上構建全量物化視圖,這可能導致因為數據插入視圖頻繁刷新,產生高昂的刷新成本,甚至視圖因為持續刷新而完全不可用。更好的做法是構建一個 T+1 條件刷新的物化視圖,存儲不變的數據,可以降低刷新成本。
另一種常見的情況是,數據倉庫存儲全量的數據,而查詢集中在最近幾個月的數據,構建全部數據的物化視圖成本過於高昂,物化視圖只構建最近幾個月數據,也能改寫絕大多數查詢。
Union 改寫會嘗試使用視圖回答部分查詢,減少查詢中實時計算的數據量。Union 改寫可以進一步應用 Aggregation 改寫,支持使用物化視圖部分數據回答聚合查詢。
例如查詢
select l_orderkey, l_partkey, l_quantity from lineitem where l_orderkey l_orderkey > 500 and l_orderkey <= 1500 and l_shipdate = l_commitdate
物化視圖
create materialized view mview8 enable query rewrite as select l_orderkey, l_partkey, l_quantity from lineitem where l_orderkey > 1000 and l_shipdate = l_commitdate
改寫結果
select l_orderkey, l_partkey, l_quantity from lineitem where l_orderkey > 500 and l_orderkey <= 1000 and l_shipdate = l_commitdate union select o.shippriority from mview8 where l_orderkey > 1000 and l_orderkey <= 1500
查詢改寫的實現
視圖改寫通常有三種查詢改寫的實現方式:
- 基於語法的改寫
- 基於規則的改寫
- 基於結構的改寫
基於語法的改寫
文本匹配或者語法匹配是最簡單的改寫方法,將查詢的文本與物化視圖的文本或語法樹進行比較,完全匹配可以進行改寫。這種改寫只能匹配完整的查詢語句或子語句,細微的變化就會導致查詢無法改寫,適用的范圍很小。基於語法的改寫雖然簡單,但是效率很高,改寫的成本可以忽略不計。
基於規則的改寫
基於規則的改寫和其他優化器規則相同,針對不同 Pattern 的查詢和視圖編寫不同的規則,尋找等價的替代關系樹。
最簡單的一條規則就是直接比較子查詢和視圖的計划,如果相同就能改寫。高級的改寫規則不需要物化視圖等同於被替換的計划,會嘗試計算補償謂詞,構建等價查詢表達式。例如 Join 改寫,比較 Join 查詢的子表達式是否和視圖 Join 的某個子表達相同或者能否從中計算出來,每一個Join子表達式都存在映射關系,最后檢查補償表達式能否從視圖中計算得到。
基於規則的改寫可以實現大量重寫,實現也比較簡單,改寫匹配速度快,但是也存在局限性。這種改寫依賴轉換規則來尋找等價關系,因此需要窮舉所有可能的轉換關系來實現復雜視圖的重寫。一些復雜的視圖不可能窮舉所有的等價關系,例如存在很多的 Join 聯接或者復雜的 Project 關系,基於規則的改寫適用的范圍取決於規則的數量。
基於結構的改寫
基於結構的改寫與基於規則的改寫相反,通過提取查詢中的特征,使用一套規則進行匹配改寫。優化器將查詢表示為 SPJG 標准形式 (Join-Select-Project-GroupBy),提取查詢中的 Join,Projects,Filters,Grouping 和 Aggregations 五種表達式,分別與物化視圖對應的表達式進行匹配和改寫。
這個方法是由微軟在 2001 年 SIGMOD 論文《Optimizing queries using materialized views: A practical, scalable solution》系統化的提出。這種方法可以改寫包含可以改寫包含 Join,Filter,Project 的任意查詢的方法,運用一系列的步驟匹配並得到補償表達式。還可以進一步改寫含有 Aggreagtion 的查詢,在需要時添加 Aggregation 節點返回進一步匯總的結果。
基於結構的改寫很容易擴展,例如改寫 Outer Join 和子查詢等,可以完成幾乎全部的改寫。但是搜索成本較高,尤其是在查詢復雜,改寫嘗試次數很多的情況下。
關於我們
AnalyticDB 是阿里巴巴自主研發、唯一經過超大規模以及核心業務驗證的 PB 級雲原生數據倉庫。
AnalyticDB 在去年10月份推出了物化視圖功能,目前的版本支持定時全量刷新和查詢改寫。AnalyticDB 完整實現了基於文本的匹配和基於結構的匹配,可以改寫包含 Join,Filter,Project,Group By 的任意查詢,支持 Aggregation Rollup,Union,Subquery,Outer join 等高級改寫規則。ADB 會在未來的幾個版本內上線基於規則的匹配,提高改寫的效率。並在未來擴展更多的改寫手段,例如 Grouping sets 改寫支持,使物化視圖有傳統數倉中 Cube 的能力。
本文為阿里雲原創內容,未經允許不得轉載。