子查詢解關聯是數據庫對關聯子查詢進行優化的一個重要手段。本文檔介紹了如何利用Window Function和Group By Aggregation對子查詢解關聯。
前提條件
集群版本需為PolarDB MySQL版8.0版本且Revision version為8.0.2.2.1及以上。您可以通過查詢版本號確認集群版本。
背景信息
關聯子查詢在分析場景中被廣泛使用,例如在決策支持基準TPC-H的22個查詢中,有超過1/3的查詢包含關聯子查詢。在沒有解關聯的時候,外層查詢的每一行數據,子查詢都會被執行一次。對于外層查詢產生數據量較多、子查詢沒有關聯索引等情況,子查詢多次執行會導致查詢耗時嚴重。子查詢解關聯將關聯子查詢變換為等價的Join語句,可以避免子查詢多次執行,同時優化器可以對Join做進一步優化。
利用Window Function解關聯
概述
假設原有查詢的通用表達形式如下:其中,T1、T2、T3為一個或多個表和視圖的集合;T2與T3之間的虛線表示子查詢中的T2與主查詢中的T3進行關聯;T1出現在主查詢中,但是不與子查詢中的T2關聯。
上圖中通用表達式的關聯子查詢,具有以下約束條件:
Scalar subquery中沒有limit,輸出為Aggregation function,且沒有distinct語法。
子查詢中的表必須是主查詢中表的一部分。
子查詢中的關聯條件必須是等值連接。主查詢包含相同語義的連接條件,且包含有子查詢中公共表的過濾條件。
子查詢中關聯條件的列是主鍵或者唯一鍵。
子查詢和主查詢都不帶有自定義函數或隨機函數。
利用Window Function將子查詢解關聯后的形式如下:
使用方法
通過系統參數loose_polar_optimizer_switch開啟利用Window function解關聯功能。具體操作請參見設置集群參數和節點參數。
參數名稱
級別
描述
loose_polar_optimizer_switch
Global、Session
查詢優化控制開關。取值范圍如下:
unnest_use_window_function:利用Window function解關聯的功能開關。
ON(默認值):開啟利用Window function解關聯功能。
OFF:關閉利用Window function解關聯功能。
unnest_use_group_by:利用Group by解關聯的功能開關,該查詢變換受基于代價的查詢變換控制。
ON(默認值):開啟利用Group by解關聯功能。
OFF:關閉利用Group by解關聯功能。
derived_merge_cost_based:derived merge功能是否受基于代價的查詢變換的控制。
OFF(默認值):derived merge功能不受基于代價的查詢變換的控制。
ON:derived merge功能受基于代價的查詢變換的控制。
示例:以TPC-H中的Q2為例,該查詢希望查找特定區域,對于指定類型和大小的零件,能夠提供最小批發價的供應商。該查詢在社區MySQL中執行需要先執行外層查詢獲取指定類型和大小的零件供應商的信息,然后對獲取的每一條數據通過子查詢計算該零件在指定區域中所有供應商最小的批發價,然后判斷獲取的供應商批發價和子查詢獲取的最小批發價是否一致。
SELECT s_acctbal, s_name, n_name, p_partkey, p_mfgr, s_address, s_phone, s_comment FROM part, supplier, partsupp, nation, region WHERE p_partkey = ps_partkey AND s_suppkey = ps_suppkey AND p_size = 30 AND p_type LIKE '%STEEL' AND s_nationkey = n_nationkey AND n_regionkey = r_regionkey AND r_name = 'ASIA' AND ps_supplycost = ( SELECT MIN(ps_supplycost) FROM partsupp, supplier, nation, region WHERE p_partkey = ps_partkey AND s_suppkey = ps_suppkey AND s_nationkey = n_nationkey AND n_regionkey = r_regionkey AND r_name = 'ASIA' ) ORDER BY s_acctbal DESC, n_name, s_name, p_partkey LIMIT 100;
利用Window function按照指定的分組方式分組計算聚合函數,在原有數據行上增加分組計算的聚合結果。對于TPC-H中的Q2,可以獲取特定區域,指定類型和大小零件的供應商同時按照零件信息進行分組計算獲得最小批發價。然后通過比較獲取的數據行中批發價和分組最小批發價比較來選擇需要的數據。Q2經過查詢變換后,獲得以下查詢的等價執行。
SELECT s_acctbal, s_name, n_name, p_partkey, p_mfgr, s_address, s_phone, s_comment FROM ( SELECT MIN(ps_supplycost) OVER(PARTITION BY ps_partkey) as win_min, ps_partkey, ps_supplycost, s_acctbal, n_name, s_name, s_address, s_phone, s_comment FROM part, partsupp, supplier, nation, region WHERE p_partkey = ps_partkey AND s_suppkey = ps_suppkey AND s_nationkey = n_nationkey AND n_regionkey = r_regionkey AND p_size = 30 AND p_type LIKE '%STEEL' AND r_name = 'ASIA') as derived WHERE ps_supplycost = derived.win_min ORDER BY s_acctbal DESC, n_name, s_name, p_partkey LIMIT 100;
通過Hint來控制滿足條件的子查詢利用Window function解關聯。
可以通過UNNEST hint來控制該查詢變換。具體變換形式如下:
UNNEST([@query_block_name] [strategy [, strategy] ...]) #表示使用Window function或Group By解關聯,并且不受polar_optimizer_switch的影響。 NO_UNNEST([@query_block_name] [strategy [, strategy] ...]) #表示禁止使用Window function或Group By解關聯,并且不受polar_optimizer_switch的影響。
其中
strategy
支持WINDOW_FUNCTION和GROUP_BY選項。示例:
#指定滿足條件的子查詢利用WINDOW_FUNCTION解關聯 SELECT ... FROM ... WHERE ... = (SELECT /*+UNNEST(WINDOW_FUNCTION)*/ agg FROM ...) SELECT /*+UNNEST(@`select#2` WINDOW_FUNCTION)*/ ... FROM ... WHERE ... = (SELECT agg FROM ...) #指定子查詢不要利用WINDOW_FUNCTION解關聯 SELECT ... FROM ... WHERE ... = (SELECT /*+NO_UNNEST(WINDOW_FUNCTION)*/ agg FROM ...) SELECT /*+NO_UNNEST(@`select#2` WINDOW_FUNCTION)*/ ... FROM ... WHERE ... = (SELECT agg FROM ...)
性能效果
使用TPC-H 10G標準數據集來測試使用Window Function進行子查詢解關聯的性能效果,發現對于Q2和Q17分別帶來了1.54和4.91倍的加速提升。如下圖所示:
利用Group By Aggregation解關聯
概述
假設原有查詢的通用表達形式如下:
上圖中通用表達式的關聯子查詢,具有以下約束條件:
Scalar subquery中沒有顯示Group By和limit,輸出為Aggregation function。
Scalar subquery在join condition、where condition或select field中。
Scalar subquery和父查詢的關聯條件必須是等值條件,且各個condition之間是AND連接。
Scalar subquery中沒有自定義函數或者隨機函數。
利用Group By Aggregation將子查詢解關聯后的形式如下:
使用方法
通過系統參數loose_polar_optimizer_switch開啟利用Group By Aggregation解關聯功能。具體操作請參見設置集群參數和節點參數。
參數名稱
級別
描述
loose_polar_optimizer_switch
Global、Session
查詢優化控制開關。取值范圍如下:
unnest_use_window_function:利用Window function解關聯的功能開關。
ON(默認值):開啟利用Window function解關聯功能。
OFF:關閉利用Window function解關聯功能。
unnest_use_group_by:利用Group by解關聯的功能開關,該查詢變換受基于代價的查詢變換控制。
ON(默認值):開啟利用Group by解關聯功能。
OFF:關閉利用Group by解關聯功能。
derived_merge_cost_based:derived merge功能是否受基于代價的查詢變換的控制。
OFF(默認值):derived merge功能不受基于代價的查詢變換的控制。
ON:derived merge功能受基于代價的查詢變換的控制。
示例:以下面查詢語句為例,希望獲取銷售訂單中大于對應商品采購總量10%的訂單明細。
SELECT * FROM sale_lineitem sl WHERE sl.sl_quantity > (SELECT 0.1 * SUM(pl.pl_quantity) FROM purchase_lineitem pl WHERE pl.pl_objectkey = sl.sl_objectkey);
如果不做任何查詢變換的話,執行引擎會迭代
sale_lineitem
表的每一行,獲取sl_objectkey
填充到子查詢中,執行子查詢獲取采購總量的10%,然后和該行數據上的數量做比較。此時,子查詢的執行次數取決于sale_lineitem
表的行數,即使在pl_objectkey
列上有索引,由于sl_objectkey
列一般存在大量重復的值,也會造成purchase_lineitem
表的大量重復掃描和重復計算。對于這種執行效率低的關聯子查詢,在PolarDB中會利用Group By Aggregation解關聯。上述查詢語句會變換為:SELECT * FROM sale_lineitem sl LEFT JOIN (SELECT (0.1 * sum(pl.pl_quantity)) AS Name_exp_1, pl.pl_objectkey AS Name_exp_2 FROM purchase_lineitem pl GROUP BY pl.pl_objectkey) derived ON derived.Name_exp_2 = sl.sl_objectkey WHERE sl.sl_quantity > derived.name_exp_1;
變換后會先對各個采購商品做分組計算,然后再與
sale_lineitem
做Join。這樣purchase_lineitem
表只需要掃描一次,避免了重復掃描和計算。這里變換后的語句可以進一步消除外連接,Join Order可以調整,提升執行效率。通過Hint來控制滿足條件的子查詢利用Group By Aggregation解關聯。
可以通過UNNEST hint來控制該查詢變換。具體變換形式如下:
UNNEST([@query_block_name] [strategy [, strategy] ...]) #表示使用Window function或Group By解關聯,并且不受polar_optimizer_switch的影響。 NO_UNNEST([@query_block_name] [strategy [, strategy] ...]) #表示禁止使用Window function或Group By解關聯,并且不受polar_optimizer_switch的影響。
其中
strategy
支持WINDOW_FUNCTION和GROUP_BY選項。示例:
#指定滿足條件的子查詢利用GROUP_BY解關聯 SELECT ... FROM ... WHERE ... = (SELECT /*+UNNEST(GROUP_BY)*/ agg FROM ...) SELECT /*+UNNEST(@`select#2` GROUP_BY)*/ ... FROM ... WHERE ... = (SELECT agg FROM ...) #指定子查詢不要利用GROUP_BY解關聯 SELECT ... FROM ... WHERE ... = (SELECT /*+NO_UNNEST(GROUP_BY)*/ agg FROM ...) SELECT /*+NO_UNNEST(@`select#2` GROUP_BY)*/ ... FROM ... WHERE ... = (SELECT agg FROM ...)