子查詢是指在父查詢的WHERE子句或HAVING子句中嵌套另一個SELECT語句的查詢,本文主要介紹PolarDB-X如何優化和執行子查詢。
基本概念
根據是否存在關聯項,子查詢可以分為非關聯子查詢和關聯子查詢。非關聯子查詢是指該子查詢的執行不依賴外部查詢的變量,這種子查詢一般只需要計算一次;而關聯子查詢中存在引用自外層查詢的變量,邏輯上,這種子查詢需要每次帶入相應的變量、計算多次。
/* 非關聯子查詢 */
SELECT * FROM lineitem WHERE l_partkey IN (SELECT p_partkey FROM part);
/* 關聯子查詢(l_suppkey 是關聯項) */
SELECT * FROM lineitem WHERE l_partkey IN (SELECT ps_partkey FROM partsupp WHERE ps_suppkey = l_suppkey);
PolarDB-X子查詢支持絕大多數的子查詢寫法,具體參見SQL使用限制。
子查詢優化
對于多數常見的子查詢形式,PolarDB-X可以將其改寫為高效的SemiJoin或類似的基于JOIN的計算方式。這樣做的好處是顯而易見的。當數據量較大時,無需真正帶入不同參數循環迭代,大大降低了執行代價。這種查詢改寫技術稱為子查詢的去關聯化(Unnesting)。
從以下子查詢去關聯化的示例中,可以看到執行計劃使用JOIN代替了子查詢。
EXPLAIN SELECT p_partkey, (
SELECT COUNT(ps_partkey) FROM partsupp WHERE ps_suppkey = p_partkey
) supplier_count FROM part;
返回執行計劃信息如下:
Project(p_partkey="p_partkey", supplier_count="CASE(IS NULL($10), 0, $9)", cor=[$cor0])
HashJoin(condition="p_partkey = ps_suppkey", type="left")
Gather(concurrent=true)
LogicalView(tables="part_[0-7]", shardCount=8, sql="SELECT * FROM `part` AS `part`")
Project(count(ps_partkey)="count(ps_partkey)", ps_suppkey="ps_suppkey", count(ps_partkey)2="count(ps_partkey)")
HashAgg(group="ps_suppkey", count(ps_partkey)="SUM(count(ps_partkey))")
Gather(concurrent=true)
LogicalView(tables="partsupp_[0-7]", shardCount=8, sql="SELECT `ps_suppkey`, COUNT(`ps_partkey`) AS `count(ps_partkey)` FROM `partsupp` AS `partsupp` GROUP BY `ps_suppkey`")
某些場景下,PolarDB-X無法將子查詢進行去關聯化,這時會采用迭代執行的方式。如果外層查詢數據量很大,迭代執行可能會非常慢。
如下示例由于
OR l_partkey < 50
的存在,導致子查詢無法被去關聯化,因而采用了迭代執行,這種情形下,建議改寫SQL去掉子查詢的OR條件。EXPLAIN SELECT * FROM lineitem WHERE l_partkey IN (SELECT ps_partkey FROM partsupp WHERE ps_suppkey = l_suppkey) OR l_partkey IS NOT
Filter(condition="IS(in,[$1])[29612489] OR l_partkey < ?0")
Gather(concurrent=true)
LogicalView(tables="QIMU_0000_GROUP,QIMU_0001_GROUP.lineitem_[0-7]", shardCount=8, sql="SELECT * FROM `lineitem` AS `lineitem`")
>> individual correlate subquery : 29612489
Gather(concurrent=true)
LogicalView(tables="QIMU_0000_GROUP,QIMU_0001_GROUP.partsupp_[0-7]", shardCount=8, sql="SELECT * FROM (SELECT `ps_partkey` FROM `partsupp` AS `partsupp` WHERE (`ps_suppkey` = `l_suppkey`)) AS `t0` WHERE (((`l_partkey` = `ps_partkey`) OR (`l_partkey` IS NULL)) OR (`ps_partkey` IS NULL))")