天天看點

MySQL 8.0.23 Hypergraph Join Optimizer代碼詳解MySQL JoinHypergraph Join Optimizer

MySQL Join

MySQL本身沒有正常意義上的執行計劃,一般情況就是通過JOIN和QEP_TAB這兩個結構組成。QEP_TAB 的全稱是Query Execution Plan Table,這個“Table“可以是實體表、記憶體表、常量表、子查詢的結果表等等。作為整個單獨JOIN執行計劃載體之前還承擔着整個執行路徑的調用和流轉,但是從8.0.20後,全面的生成了獨立的Iterator執行器引擎模式。在8.0.22中,又引入了AccessPath概念,真正的生成了獨立的執行計劃,進而進一步做到了優化過程到樹型執行計劃,最後到Iterator載體在執行引擎中的執行。

MySQL原始的Join都是依賴于QEP_TAB清單,因為原來MySQL并不支援其他形态的Join結構,隻支援左深樹,那很容易直接使用數組來表示就可以了。優化器在生成執行計劃隻需要在QEP_TAB上增加JOIN的屬性op_type,就可以遞歸去使用不同的Join方法和表通路方式了。

// Operation between the previous QEP_TAB and this one.
  enum enum_op_type {
    // Regular nested loop.
    OT_NONE,

    // Aggregate (GROUP BY).
    OT_AGGREGATE,

    // Various temporary table operations, used at the end of the join.
    OT_MATERIALIZE,
    OT_AGGREGATE_THEN_MATERIALIZE,
    OT_AGGREGATE_INTO_TMP_TABLE,
    OT_WINDOWING_FUNCTION,

    // Block-nested loop (rewritten to hash join).
    OT_BNL,

    // Batch key access.
    OT_BKA
  } op_type = OT_NONE;           

Hypergraph Join Optimizer

官方共分了11個Patch來送出對于Join優化器的增強,當然其中包含了對優化器和執行器分離更進一步重構,我們先來看看官方是怎麼送出這樣的重大重構的。

[Basic] 動态規劃查詢超圖算法(DPhyp-Hypergraph partitioning algorithm)

官方首先實作了基于DPhyp的動态規劃查詢超圖算法,論文可以搜尋《Dynamic Programming Strikes Back》。資料庫中關于Join ordering算法有很多,引用2,3中的作者已經做了詳盡的解釋。我這裡隻做簡單的介紹。

每一個Query,都可以定義為一個無向Query Graph,包括查詢中的所有關系R1,R2,...,Rn作為節點;連接配接謂詞表達式作為邊,如a1 = a2 (a1 ∈ Ri,a2 ∈ Rj);連接配接謂詞中包含常量會形成自邊(self-edge),如a1 = const (a1 ∈ Ri);大部分的自邊在Join算法裡是不考慮的,因為它會被下推下圖。例如對于 select * from Student s, Attend a, Lecture l, Professor p where s.sno = a.asno and a.alno = l.lno and l.lpno = p.pno and p.pname = 'Larson',有如下Query Graph結構:

MySQL 8.0.23 Hypergraph Join Optimizer代碼詳解MySQL JoinHypergraph Join Optimizer

對于Join Tree,一般會有以下幾種:left-deep tree、right-deep tree、zigzag tree和bushy tree。前三種是屬于線性Join tree。MySQL之前采取左深樹,為了考慮更好的支援Hash Join和NestLoop Join的選擇,現在開始考慮Bushy Tree了。為了避免任何時候的笛卡爾積Join,線性Join的Join ordering算法通常很簡單。那麼為什麼要引入複雜的Bushy Tree。假設定義Query(R1, R2, R3)有如下屬性,y |R1| = 10, |R2| = 20, |R3| = 20, |R4| = 10, f1,2 = 0.01, f2,3 = 0.5, f3,4 = 0.01。||代表行數,fn,m代表Rn和Rm的選擇率,可以看到Bushy Tree有更好的執行效率。

MySQL 8.0.23 Hypergraph Join Optimizer代碼詳解MySQL JoinHypergraph Join Optimizer

不過遺憾的是,Bushy Tree的搜尋可能性非常大:

MySQL 8.0.23 Hypergraph Join Optimizer代碼詳解MySQL JoinHypergraph Join Optimizer

是以,原始左深樹使用的Greedy Heuristics算法,在Bushy Tree下,計算Join Ordering通常使用動态規劃算法(DPccp和DPhyp)。

DPccp的算法如下:

MySQL 8.0.23 Hypergraph Join Optimizer代碼詳解MySQL JoinHypergraph Join Optimizer

但是DPccp有很多限制:複雜謂詞,涉及到多個表(R1,R2,R3)做為連接配接,例如:R1.a + R2.b + R3.c = R4.d + R5.e + R6.f ;隻支援inner joins;是以引入了新的基于Hypergraph的算法DPhyp。

select *
from R1 r1, R2 r2, R3 r3,
R4 r4, R5 r5, R6 r6
where r1.a=r2.a and r2.b=r3.c and
r4.d=r5.d and r5.e=r6.e and
abs(r1.f + r3.f )
= abs(r4.g + r6.g)           

介紹算法先介紹下基本概念超圖(hypergraph)相比普通的圖,其特點是圖中的節點是一個集合,稱為超節點(hypernode),圖中的邊所連接配接的是超節點,即連接配接兩個集合。這類邊稱為超邊(hyperedge)。超圖就是由超節點和超邊作為最基本元素而構成的。有了超圖那麼上面的Join Graph可以變成:

MySQL 8.0.23 Hypergraph Join Optimizer代碼詳解MySQL JoinHypergraph Join Optimizer

由于使用DPccp和Top-Down Partition Search,不能夠解決outer join,antijoin的不能自由重排的算法。

MySQL目前采用Bitmap(64bit)來表示,假設Join table個數不會超過61個,看下它的定義

+struct Hyperedge {
+  // The endpoints (hypernodes) of this hyperedge. See the comment about
+  // duplicated edges in Node.
+  //
+  // left and right may not overlap, and both must have at least one bit set.
+  NodeMap left;
+  NodeMap right;
+};
+
+struct Hypergraph {
+  std::vector<Node> nodes;  // Maximum 8*sizeof(NodeMap) elements.
+  std::vector<Hyperedge> edges;
+
+  void AddNode();
+  void AddEdge(NodeMap left, NodeMap right);
+};           

基本算法流程如下:

1.找到一個圖中種子節點Ri

2.不斷增加i去找hyperedges,不考慮不連接配接的和已經處理過的。

3 對于每一個連通子圖subgraph (csg),再重複1和2步驟,找出一個仍然可以連通子圖(complement, cmp),然後連接配接這個圖的cmp成為更大的連通子圖(csg-cmp-pair).

4. 當找到一個csg-cmp-pair,就形成一個可以進行估算的subjoin。

MySQL 8.0.23 Hypergraph Join Optimizer代碼詳解MySQL JoinHypergraph Join Optimizer

感興趣可以閱讀相應的論文和MySQL的代碼(sql/join_optimizer)。

QEP_TAB和執行器Iterator解藕,重新來設定InnoDB row buffer

總所周知,QEB_TAB結構上承載了很多資訊,除了上面表通路和Join方法的資訊之外,還有InnoDB row buffer、表通路的優化通路方式(ref/range/loose scan/first match/materialize)、附加屬性(having/distinct/sort/icp/lateral derived/mrr/cte)、基本實體表結構TABLE_LIST等。作為删除QEP_TAB的基礎,首先先做了和執行器的解藕工作,Iterator和QEP_TAB分離。

class TableScanIterator final : public TableRowIterator {
  public:
-  // Accepts nullptr for qep_tab; qep_tab is used only for setting up record
-  // buffers.
-  //
-  // The pushed condition can be nullptr.
+  // “expected_rows” is used for scaling the record buffer.
+  // If zero or less, no record buffer will be set up.
   //
   // "examined_rows", if not nullptr, is incremented for each successful Read().
-  TableScanIterator(THD *thd, TABLE *table, QEP_TAB *qep_tab,
+  TableScanIterator(THD *thd, TABLE *table, double expected_rows,
                     ha_rows *examined_rows);           

接下來解藕

-static bool init_index_and_record_buffer(const QEP_TAB *qep_tab, handler *file,
+static bool init_index(TABLE *table, handler *file, uint idx, bool sorted) {

-bool set_record_buffer(const QEP_TAB *tab);
+bool set_record_buffer(TABLE *table, double expected_rows_to_fetch);

=>

-  return init_index_and_record_buffer(m_qep_tab, m_qep_tab->table()->file,
-                                      m_ref->key, m_use_order);
+  if (table()->file->inited) return false;
+  if (init_index(table(), table()->file, m_ref->key, m_use_order)) {
+    return true;
+  }
+  return set_record_buffer(table(), m_expected_rows);           

實作CostingReceiver和轉化查詢塊select_lex成為超圖hypergraph

MySQL 8.0.23提供了支援hypergraph的優化器模型的第一個原型版本,通過set optimizer_switch="hypergraph_optimizer=on";來打開,主要和原有的優化器差別在于:

  • 不再局限于左深樹的執行計劃
  • 用DPhyp動态規劃算法代替了強力算和啟發式的剪枝方式,減少了搜尋空間,當然還有一些限制
  • Hash join成為主要的選擇方式
  • 直接和AccessPath互通,而非直接生成Iterators

主要通過FindBestQueryPlan函數來實作,邏輯如下:

  • 先判斷是否屬于新優化器可以支援的Query文法(CheckSupportedQuery),不支援的直接傳回錯誤ER_HYPERGRAPH_NOT_SUPPORTED_YET
  • 轉化top_join_list變成JoinHypergraph結構。由于Hypergraph是比較獨立的算法層面的實作,JoinHypergraph結構用來更好的把資料庫的結構包裝到Hypergraph的edges和nodes的概念上的。
  • 通過EnumerateAllConnectedPartitions實作論文中的DPhyp算法
  • CostingReceiver類包含了過去JOIN planning的主要邏輯,包括根據cost選擇相應的通路路徑,根據DPhyp生成的子計劃進行評估,保留cost最小的子計劃。
  • 得到root_path後,接下來處理group/agg/having/sort/limit的。對于Group by操作,目前Hypergraph使用sorting first + streaming aggregation的方式。

最後通過CreateIteratorFromAccessPath函數生成對應的執行Iterator樹,在Iterator執行器中執行。

舉例說明:

兩個連通子圖
root:test> explain format=tree select * from t1,t2,t3,t4 where t2.f2 = t1.a and t1.a = t3.a;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Hash cartesian product (no condition)  (cost=1.83 rows=2)
    -> Inner hash join (t2.f2 = t1.a)  (cost=1.55 rows=2)
        -> Table scan on t2  (cost=0.25 rows=2)
        -> Hash
            -> Inner hash join (t1.a = t3.a)  (cost=1.27 rows=1)
                -> Table scan on t1  (cost=1.00 rows=1)
                -> Hash
                    -> Table scan on t3  (cost=0.25 rows=1)
    -> Hash
        -> Table scan on t4  (cost=0.25 rows=1)
 |

一個連通子圖
root:test> explain format=tree select * from t1,t2,t3,t4 where t2.f2 = t1.a and t1.a = t3.a and t2.f2 = t4.pk and t1.a = t4.pk;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (t2.f2 = t4.pk), (t1.a = t4.pk)  (cost=1.83 rows=2)
    -> Inner hash join (t2.f2 = t1.a)  (cost=1.55 rows=2)
        -> Table scan on t2  (cost=0.25 rows=2)
        -> Hash
            -> Inner hash join (t1.a = t3.a)  (cost=1.27 rows=1)
                -> Table scan on t1  (cost=1.00 rows=1)
                -> Hash
                    -> Table scan on t3  (cost=0.25 rows=1)
    -> Hash
        -> Table scan on t4  (cost=0.25 rows=1)
 |           

參考資料:

《MySQL 8.0 新的火山模型執行器》 《SIGMOD'08|Join Ordering:應對複雜謂詞和非内連接配接的場景》