本文說明調整 Spanner Graph 查詢效能的最佳做法,包括下列最佳化措施:
- 避免對節點和邊緣的輸入資料表進行完整掃描。
- 減少查詢需要從儲存空間讀取的資料量。
- 縮減中繼資料的大小。
從基數較低的節點開始
編寫路徑遍歷時,請從基數較低的節點開始。 這種做法可縮小中間結果集,並加快查詢執行速度。
舉例來說,下列查詢具有相同的語意:
正向邊緣遍歷:
GRAPH FinGraph MATCH (p:Person {name:"Alex"})-[:Owns]->(a:Account {is_blocked: true}) RETURN p.id AS person_id, a.id AS account_id;反向邊緣遍歷:
GRAPH FinGraph MATCH (a:Account {is_blocked:true})<-[:Owns]-(p:Person {name: "Alex"}) RETURN p.id AS person_id, a.id AS account_id;
假設名為「Alex」的人數少於遭封鎖的帳戶數,建議您在正向邊緣遍歷中撰寫這項查詢。
對於路徑長度可變的遍歷作業,從基數較低的節點開始尤其重要。以下範例顯示建議做法,找出與指定帳戶相差三筆轉移記錄的帳戶。
GRAPH FinGraph
MATCH (:Account {id: 7})-[:Transfers]->{1,3}(a:Account)
RETURN a.id;
預設指定所有標籤
如果省略標籤,Spanner Graph 會推斷符合資格的節點和邊緣標籤。建議您盡可能為所有節點和邊緣指定標籤,因為系統不一定每次都能進行這項推論,而且可能會掃描不必要的標籤。
單一 MATCH 陳述式
以下範例會找出最多經過 3 次轉移,與指定帳戶連結的帳戶:
GRAPH FinGraph
MATCH (src:Account {id: 7})-[:Transfers]->{1,3}(dst:Account)
RETURN dst.id;
MATCH 陳述式
當節點和邊緣參照相同元素,但位於 MATCH 陳述式中時,請指定標籤。
以下範例說明建議做法:
GRAPH FinGraph
MATCH (acct:Account {id: 7})-[:Transfers]->{1,3}(other_acct:Account)
RETURN acct, COUNT(DISTINCT other_acct) AS related_accts
GROUP BY acct
NEXT
MATCH (acct:Account)<-[:Owns]-(p:Person)
RETURN p.id AS person, acct.id AS acct, related_accts;
使用 IS_FIRST 最佳化查詢
您可以使用 IS_FIRST 函式,在圖表中對邊緣取樣並限制遍歷,藉此提升查詢效能。這項函式有助於處理高基數節點,並最佳化多跳查詢。
如果指定的樣本大小太小,查詢可能不會傳回任何資料。 因此,您可能需要嘗試不同的樣本大小,找出傳回資料和查詢效能的最佳平衡點。
這些 IS_FIRST 範例使用 FinGraph,這是包含 Account 節點和 Transfers 邊緣的金融圖表,用於轉移資金。如要建立 FinGraph 並使用該項目執行範例查詢,請參閱「設定及查詢 Spanner Graph」。
限制遍歷的邊緣,以提升查詢效能
查詢圖表時,某些節點的傳入或傳出邊緣數量,可能遠多於其他節點。這些高基數節點有時稱為超級節點或中樞節點。超級節點可能會導致效能問題,因為透過超級節點的遍歷可能涉及處理大量資料,進而導致資料偏斜和執行時間過長。
如要最佳化含有超級節點的圖形查詢,請在 FILTER 子句中使用 IS_FIRST 函式,限制查詢從節點遍歷的邊緣數量。因為 FinGraph 中的帳戶交易次數可能遠高於其他帳戶,因此您可能會使用 IS_FIRST 避免查詢效率不彰。如果您不需要從超級節點完整列舉所有連線,這種做法就特別實用。
下列查詢會找出直接或間接從遭封鎖帳戶 (a1) 接收轉移的帳戶 (a2)。查詢會使用 IS_FIRST,限制每個 Account 要考慮的 Transfers 邊緣數量,避免帳戶有大量轉移時效能緩慢。
GRAPH FinGraph
MATCH
(a1:Account {is_blocked: true})
-[e:Transfers WHERE e IN
{
MATCH -[selected_e:Transfers]->
FILTER IS_FIRST(@max_transfers_per_account) OVER (
PARTITION BY SOURCE_NODE_ID(selected_e)
ORDER BY selected_e.create_time DESC)
RETURN selected_e
}
]->{1,5}
(a2:Account)
RETURN a1.id AS src_id, a2.id AS dst_id;
本範例使用下列項目:
@max_transfers_per_account:查詢參數,用於指定每個帳戶 (a1) 要考量的Transfers邊緣數量上限。PARTITION BY SOURCE_NODE_ID(selected_e):確保每個帳戶 (a1) 都有各自的IS_FIRST限制。ORDER BY selected_e.create_time DESC:指定傳回最近的轉移作業。
取樣中繼節點,以最佳化多跳查詢
您也可以使用 IS_FIRST 對多重躍點查詢中的中繼節點取樣,藉此提升查詢效率。這項技術會限制查詢為每個中繼節點考量的路徑數量,藉此提升效率。如要這麼做,請將多重躍點查詢拆分為多個以 NEXT 分隔的 MATCH 陳述式,並在需要取樣的中間點套用 IS_FIRST:
GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->(a2:Account)
FILTER IS_FIRST(1) OVER (PARTITION BY a2)
RETURN a1, a2
NEXT
MATCH (a2)-[e2:Transfers]->(a3:Account)
RETURN a1.id AS src_id, a2.id AS mid_id, a3.id AS dst_id;
如要瞭解 IS_FIRST 如何最佳化這項查詢,請按照下列步驟操作:
子句
FILTER IS_FIRST(1) OVER (PARTITION BY a2)會套用至第一個MATCH陳述式。對於每個中繼帳戶節點 (
a2),IS_FIRST只會考慮第一個傳入的Transfers邊緣 (e1),減少第二個MATCH陳述式中要探索的路徑數量。整體而言,兩跳查詢的效率會有所提升,因為第二個
MATCH不會處理不必要的資料,尤其是當a2有許多傳入的轉移作業時。
使用因式分解執行來最佳化查詢
因式分解會遍歷圖表模式並建立重複的中間結果,藉此最佳化 Spanner Graph 查詢效能。
圖形查詢可以因式分解模式執行,在周遊路徑的其餘部分之前,先重複資料量高的路徑前置字元。這項最佳化措施可減少圖形查詢執行期間重複的鍵比較或雜湊表探查,進而提升查詢效能。如要將查詢分解為多個查詢,請在個別模式遍歷或查詢層級中加入 @{factorized_mode} 提示。
將 factorized_mode 設為下列其中一項:
factorize_left:最佳化圖形遍歷,其中許多路徑會匯聚在幾個節點上。Spanner Graph 會依目的地節點 ID 重複資料路徑,然後擷取節點屬性,減少多餘的工作。factorize_both:針對共用節點的多個子路徑,最佳化非線性查詢。Spanner Graph 會避免為每個路徑獨立產生中繼結果。而是先計算每個子路徑一次,然後再合併,將結果的交叉積延遲到最後。適用於三角形或分支等模式。
如要進一步瞭解遍歷提示,請參閱「圖形提示」。
下列範例說明如何使用因數分解模式提示,提升圖表查詢效能。如要執行程式碼範例,請在「設定及查詢 Spanner Graph」中建立 FinGraph 結構定義。
減少線性查詢中的中繼結果
詐欺帳戶通常會收到大量交易。這類查詢會產生大量的中間結果,其中許多都是重複項目,因為這些結果會匯聚到一小組目的地帳戶。從這些多餘的中間結果擷取詐欺節點屬性需要不必要的運算,這可能會大幅降低查詢效能。
@{factorized_mode} 提示會最佳化查詢執行作業,解決這個問題。以下範例說明如何使用這項提示擷取帳戶和交易明細。這項功能會追蹤從已知遭封鎖帳戶發起的轉帳交易,找出可能涉及詐欺的帳戶 (最多三層)。您可以在第二個分頁中查看這項查詢的未最佳化版本。
因式分解查詢
GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->{1,3}@{factorized_mode=factorize_left}(a2:Account)
LET total_amount = SUM(e1.amount)
RETURN a2.id, a2.create_time, total_amount;
未經最佳化的查詢
GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->{1,3}(a2:Account)
LET total_amount = SUM(e1.amount)
RETURN a2.id, a2.create_time, total_amount;
當不同目的地節點 a2 的數量遠小於通往 a2 的路徑數量時,這項技術可避免多餘的工作。這在詐欺網路中很常見,中介機構會將款項轉給少數收款人。因式分解提示會執行下列動作:
找出所有路徑,這些路徑包含一到三個從封鎖節點開始的邊緣,以及這些路徑的轉乘總和。
決定不重複目的地節點
a2的數量。只會擷取每個不同目的地節點
a2的create_time一次。將每個節點的
create_time與通往a2的路徑清單合併,形成查詢結果。create_timea2
在複雜的非線性查詢中延後產生中繼結果
圖形查詢通常結構複雜,由多個線性子路徑模式組成。以下範例顯示的查詢會分析來自帳戶的交易,其中 id 等於 1。這項查詢會依據所有目的地帳戶的 create_time,將其歸入三個不同的類別:
- 過去 30 天內
- 30 至 90 天前
- 超過 90 天
查詢會傳回所有三元組,以及這些交易的總交易金額。
@{factorized_mode=factorize_both} 提示可避免早期具體化中繼結果,進而最佳化查詢執行作業。因為在 MATCH 子句中,所有子路徑都會根據起始節點 a 的值,有條件地保持獨立。factorize_both 會獨立評估前兩個子路徑,不會重複評估最後一個子路徑。
因式分解查詢
GRAPH FinGraph
MATCH (a:Account {id: 1})-[e1:Transfers]->(a1:Account),
@{factorized_mode=factorize_both}(a:Account)-[e2:Transfers]->(a2:Account),
(a:Account)-[e3:Transfers]->(a3:Account)
WHERE e1.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
AND e2.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
AND e2.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND e3.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
LET total_amount = e1.amount + e2.amount + e3.amount
RETURN a1.id as a1_id, a2.id as a2_id, a3.id as a3_id, total_amount;
未經最佳化的查詢
GRAPH FinGraph
MATCH (a:Account {id: 1})-[e1:Transfers]->(a1:Account),
(a:Account)-[e2:Transfers]->(a2:Account),
(a:Account)-[e3:Transfers]->(a3:Account)
WHERE e1.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
AND e2.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
AND e2.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND e3.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
LET total_amount = e1.amount + e2.amount + e3.amount
RETURN a1.id as a1_id, a2.id as a2_id, a3.id as a3_id, total_amount;
如果起始帳戶的 id 等於 1,每個外向 Transfers 邊緣都會導向多個獨立目的地節點。請參考以下範例:
M是指在(-inf, 90 DAYS)範圍內有轉移記錄的帳戶數量 (a1)N是指在[90 DAYS, 30 DAYS)範圍內有轉移記錄的帳戶數量 (a2)K是指在[30 DAYS, +inf)範圍內有轉移記錄的帳戶數量 (a3)
未經過最佳化的查詢會提早產生中繼結果,並計算第二個子路徑 M 次數和第三個子路徑 M * N 次數。相較之下,因式分解版本會執行下列動作,進而提升查詢執行效率:
取得第一個子路徑的
M目的地節點 (a1),並暫時儲存一組a1節點。只取得一次第二個子路徑的
N目的地節點 (a2),並暫時儲存一組a2節點。只取得最後一個子路徑的
K目的地節點 (a3) 一次。在臨時中繼結果之間執行交叉乘積,以建立
MxNxK記錄的乘積。