全部產品
Search
文件中心

PolarDB:查詢執行器介紹

更新時間:Jul 06, 2024

本文介紹PolarDB-X 1.0的SQL執行器如何執行SQL中無法下推的部分。

基本概念

SQL執行器是PolarDB-X 1.0中執行邏輯層運算元的組件。對於簡單的點查SQL,往往可以整體下推儲存層MySQL執行,因而感覺不到執行器的存在,MySQL的結果經過簡單的解包封包又被回傳給使用者。但是對於較複雜的SQL,往往無法將SQL中的運算元全部下推,這時候就需要PolarDB-X 1.0執行器執行無法下推的計算。

例如,對於如下查詢SQL:

SELECT l_orderkey, sum(l_extendedprice *(1 - l_discount)) AS revenue
FROM CUSTOMER, ORDERS, LINEITEM
WHERE c_mktsegment = 'AUTOMOBILE'
  and c_custkey = o_custkey
  and l_orderkey = o_orderkey
  and o_orderdate < '1995-03-13'
  and l_shipdate > '1995-03-13'
GROUP BY l_orderkey;

通過EXPLAIN命令看到PolarDB-X 1.0的執行計畫如下:

HashAgg(group="l_orderkey", revenue="SUM(*)")
  HashJoin(condition="o_custkey = c_custkey", type="inner")
    Gather(concurrent=true)
      LogicalView(tables="ORDERS_[0-7],LINEITEM_[0-7]", shardCount=8, sql="SELECT `ORDERS`.`o_custkey`, `LINEITEM`.`l_orderkey`, (`LINEITEM`.`l_extendedprice` * (? - `LINEITEM`.`l_discount`)) AS `x` FROM `ORDERS` AS `ORDERS` INNER JOIN `LINEITEM` AS `LINEITEM` ON (((`ORDERS`.`o_orderkey` = `LINEITEM`.`l_orderkey`) AND (`ORDERS`.`o_orderdate` < ?)) AND (`LINEITEM`.`l_shipdate` > ?))")
    Gather(concurrent=true)
      LogicalView(tables="CUSTOMER_[0-7]", shardCount=8, sql="SELECT `c_custkey` FROM `CUSTOMER` AS `CUSTOMER` WHERE (`c_mktsegment` = ?)")

如下圖所示,LogicalView的SQL在執行時被下發給MySQL,而不能下推的部分(除LogicalView以外的運算元)由PolarDB-X 1.0執行器進行計算,得到終端使用者SQL需要的結果。

查詢執行器

Volcano執行模型

PolarDB-X 1.0和很多資料庫一樣採用Volcano執行模型。所有運算元都定義了open()next()等介面,運算元根據執行計畫組合成一棵運算元樹,上層運算元通過調用下層運算元的next()介面的取出結果,完成該運算元的計算。最終頂層運算元產生使用者需要的結果並返回給用戶端。

下面的例子中,假設HashJoin運算元已經完成構建雜湊表。當上層的Project運算元請求資料時,HashJoin首先向下層Gather請求一批資料,然後查表得到JOIN結果,再返回給Project運算元。

Volcano執行模型

某些情況下,運算元需要將資料全部讀取並緩衝在記憶體中,該過程被稱為物化,例如,HashJoin運算元需要讀取內表的全部資料,並在記憶體中構建出雜湊表。其他類似的運算元還有HashAgg(彙總)、MemSort(排序)等。

由於記憶體資源是有限的,如果物化的資料量超出單條查詢限制,或者使用的總記憶體超出PolarDB-X 1.0節點記憶體限制,將會引起記憶體不足(OUT_OF_MEMORY)報錯。

並行查詢

並行查詢(Parallel Query)指利用多線程並存執行使用者的複雜查詢。

說明 該功能僅在PolarDB-X 1.0標準版及企業版上提供,入門版由於硬體規格限制,不提供該項功能。

並行查詢的執行計畫相比原來有所改動。例如,還是以上面的查詢為例,它的並存執行計劃如下所示:

Gather(parallel=true)
  ParallelHashAgg(group="o_orderdate,o_shippriority,l_orderkey", revenue="SUM(*)")
    ParallelHashJoin(condition="o_custkey = c_custkey", type="inner")
      LogicalView(tables="ORDERS_[0-7],LINEITEM_[0-7]", shardCount=8, sql="SELECT `ORDERS`.`o_custkey`, `ORDERS`.`o_orderdate`, `ORDERS`.`o_shippriority`, `LINEITEM`.`l_orderkey`, (`LINEITEM`.`l_extendedprice` * (? - `LINEITEM`.`l_discount`)) AS `x` FROM `ORDERS` AS `ORDERS` INNER JOIN `LINEITEM` AS `LINEITEM` ON (((`ORDERS`.`o_orderkey` = `LINEITEM`.`l_orderkey`) AND (`ORDERS`.`o_orderdate` < ?)) AND (`LINEITEM`.`l_shipdate` > ?))", parallel=true)
      LogicalView(tables="CUSTOMER_[0-7]", shardCount=8, sql="SELECT `c_custkey` FROM `CUSTOMER` AS `CUSTOMER` WHERE (`c_mktsegment` = ?)", parallel=true)
並行查詢

可以看出,並存執行計劃中Gather運算元的位置被拉高了,這也意味著Gather下方的運算元都會以並行方式執行,直到Gather時才被匯總成在一起。

執行時,Gather下方的運算元會執行個體化出多個執行執行個體,分別對應一個並行度。並行度預設等於單台機器的核心數,標準版執行個體預設並行度為8,企業版執行個體預設並行度為16。

執行過程的診斷分析

除了上文提到的EXPLAIN指令,還有如下幾個指令能協助分析效能問題:

  • EXPLAIN ANALYZE指令用於分析PolarDB-X 1.0 Server中各運算元執行的效能指標。
  • EXPLAIN EXECUTE指令用於輸出MySQL的EXPLAIN結果(並匯總輸出)。

如下是以上文提到的查詢為例,介紹如何分析一條查詢的效能問題。

執行EXPLAIN ANALYZE得到如下結果(刪除了一些無關的資訊):

explain analyze select l_orderkey, sum(l_extendedprice *(1 - l_discount)) as revenue from CUSTOMER, ORDERS, LINEITEM where c_mktsegment = 'AUTOMOBILE' and c_custkey = o_custkey and l_orderkey = o_orderkey and o_orderdate < '1995-03-13' and l_shipdate > '1995-03-13' group by l_orderkey;

HashAgg(group="o_orderdate,o_shippriority,l_orderkey", revenue="SUM(*)")
... actual time = 23.916 + 0.000, actual rowcount = 11479, actual memory = 1048576, instances = 1 ...
  HashJoin(condition="o_custkey = c_custkey", type="inner")
  ... actual time = 0.290 + 23.584, actual rowcount = 30266, actual memory = 1048576, instances = 1 ...
    Gather(concurrent=true)
    ... actual time = 0.000 + 23.556, actual rowcount = 151186, actual memory = 0, instances = 1 ...
      LogicalView(tables="ORDERS_[0-7],LINEITEM_[0-7]", shardCount=8, sql="SELECT `ORDERS`.`o_custkey`, `ORDERS`.`o_orderdate`, `ORDERS`.`o_shippriority`, `LINEITEM`.`l_orderkey`, (`LINEITEM`.`l_extendedprice` * (? - `LINEITEM`.`l_discount`)) AS `x` FROM `ORDERS` AS `ORDERS` INNER JOIN `LINEITEM` AS `LINEITEM` ON (((`ORDERS`.`o_orderkey` = `LINEITEM`.`l_orderkey`) AND (`ORDERS`.`o_orderdate` < ?)) AND (`LINEITEM`.`l_shipdate` > ?))")
      ... actual time = 0.000 + 23.556, actual rowcount = 151186, actual memory = 0, instances = 4 ...
    Gather(concurrent=true)
    ... actual time = 0.000 + 0.282, actual rowcount = 29752, actual memory = 0, instances = 1 ...
      LogicalView(tables="CUSTOMER_[0-7]", shardCount=8, sql="SELECT `c_custkey` FROM `CUSTOMER` AS `CUSTOMER` WHERE (`c_mktsegment` = ?)")
      ... actual time = 0.000 + 0.282, actual rowcount = 29752, actual memory = 0, instances = 4 ...

其中:

  • actual time表示實際執行耗時(其中包含子運算元的耗時),加號(+)左邊表示open(準備資料)耗時,右邊表示next(輸出資料)耗時。
  • actual rowcount表示輸出的行數。
  • actual memory表示運算元使用的記憶體空間大小(單位為Bytes)。
  • instances表示執行個體數,非並行查詢時始終為1,對於並行運算元每個並行度對應一個執行個體。如果執行個體數不等於1,actual time,actual rowcount,actual memory代表多個執行個體並存執行的總實際執行耗時、總輸出行數、總記憶體使用量量。
說明 當使用並行查詢時,上述的運算元耗時、輸出行數等資訊均為運算元多個執行個體的累加。例如actual time = 20,instances = 8,表示該運算元有8個執行個體並存執行,平均耗時為2.5s。

以上面的輸出為例,解讀如下:

  • HashAgg運算元open耗時為23.916s,用於擷取下層HashJoin的輸出、並對輸出的所有資料做分組和彙總。其中的23.601s都用在了擷取了下層輸出上,只有約0.3s用於分組彙總。
  • HashJoin運算元open耗時0.290s,用於拉取右表(下方的Gather)資料並構建雜湊表;next耗時23.584s,用於拉取左表資料以及查詢雜湊表得到JOIN結果。
  • Gather運算元僅僅用於匯總多個結果集,通常代價很低。
  • 左側(上方)的LogicalView拉取資料消耗了23.556s,可判斷這裡是查詢的效能瓶頸。
  • 右側(下方)的LogicalView拉取資料消耗了0.282s。

綜上,效能瓶頸在左邊的LogicalView上。從執行計畫中可以看到,它是對ORDERS、LINEITEM的JOIN查詢,這條查詢MySQL執行速度較慢。

您可以通過如下EXPLAIN EXECUTE語句查看MySQL EXPLAIN結果:

MySQL EXPLAIN結果

上圖中,紅色方框對應左邊的LogicalView的下推查詢,藍色方框對應右邊LogicalView的下推查詢。