時序數據庫Influx-IOx源碼學習十一(SQL的解析)

InfluxDB是一個由InfluxData開發的開源時序數據庫,專一於海量時序數據的高性能讀、寫、高效存儲與實時分析等,在DB-Engines Ranking時序型數據庫排行榜上常年排名第一。sql

InfluxDB能夠說是當之無愧的佼佼者,但 InfluxDB CTO Paul 在 2020/12/10 號在博客中發表一篇名爲:Announcing InfluxDB IOx – The Future Core of InfluxDB Built with Rust and Arrow的文章,介紹了一個新項目 InfluxDB IOx,InfluxDB 的下一代時序引擎。數據庫

接下來,我將連載對於InfluxDB IOx的源碼解析過程,歡迎各位批評指正,聯繫方式見文章末尾。apache


上一章介紹了查詢的主流程,詳情見:數組

https://my.oschina.net/u/3374539/blog/5034513微信

這章記錄一下SQL的解析過程。數據結構


Influx Iox 使用了 Fusion 做爲sql的查詢引擎(Funsion目前是apache arrow的一個子項目)。總體查詢架構如圖所示: 架構

我在網上找了找Fusion相關的文檔,沒有找到比較詳細一些的說明,因此只能本身總結了。性能

一般來說,sql的語句解析分爲兩個大的步驟,分別是:優化

  • 邏輯執行計劃(LogicPlan)
  • 物理執行計劃(PhysicalPlan)

邏輯執行計劃(LogicPlan)

LogicPlan主要是用來描述用戶輸入的SQL都包含了一些什麼內容,例如: selet * from table where a = 1 ,要轉換到類能解釋的模型上就會成爲:ui

class SelectClass{
   path : "*",
   from: "table",
   where: eq(a , 1)
}

這是很是簡單的狀況,sql中還會摻雜大量的關鍵字,好比SUMJOINORDER BY等等,若是須要把全部東西都記錄下來,可能類圖看起來就像是這樣:

在Fusion中,有一個名爲parser.rs的解析器他的主要工做就是將純SQL解析爲一個程序基本能夠理解的結構。主要過程有:

  1. 定義全部的關鍵詞,可以識別出來在sql語句中的含義。好比 SELECT、INSERT 等等
  2. 遍歷sql語句每一個空格或者遇到表達式切分一次,而後在定義的關鍵詞裏查找是否爲關鍵字
  3. 使用一個名叫TOKEN的枚舉來表示每一個節點不一樣的含義,好比EQ,NEQ,COMMA等等
  4. 最後存儲到一個數組當中,數據結構大體以下:
Ok([Word(Word { value: "select", quote_style: None, keyword: SELECT }), Whitespace(Space), Mult, Whitespace(Space), Word(Word { value: "from", quote_style: None, keyword: FROM }), Whitespace(Space), Word(Word { value: "table1", quote_style: None, keyword: NoKeyword }), Whitespace(Space), Word(Word { value: "where", quote_style: None, keyword: WHERE }), Whitespace(Space), Word(Word { value: "a", quote_style: None, keyword: NoKeyword }), Whitespace(Space), Eq, Whitespace(Space), Number("1", false)])
  1. 按照不一樣的開頭關鍵字去執行不一樣的分支。好比CREATE 和 SELECT 確定後面的解析方式不同。
  2. 封裝成不一樣的LogicPlan子類。
pub enum LogicalPlan {
    //基本就是純select
    Projection {
      ... 省略
    },
    //帶有filter的
    Filter {
        ... 省略
    },
    //是聚合的
    Aggregate {
         ... 省略
    },
    //帶排序的
    Sort {
         ... 省略
    },
     ... 省略
}

邏輯執行計劃的優化

在用戶輸入一段sql以後,每每他並不會意識到本身是否真的輸入了很是有意義的東西,而且他也不會清楚程序到底用什麼樣的組織方式會讓程序執行的更快,因此通常來說,用戶輸入的sql是最不可信的,還須要再次執行優化。

舉個簡單的例子,假如用戶輸入select * from table where is_valid = true and is_valid !=false ,很明顯能夠在執行前優化成 select * from table where is_valid = true,從而減小在實際執行時,對數據庫的操做。

從圖中能夠看到Fusion提供了5種優化,有興趣的的能夠本身瞭解,或者之後再作分析。

物理執行計劃

物理執行計劃,我理解就是將用戶輸入的文字性描述的信息專爲真正的資源來存儲,好比用戶寫入的是from t1,那麼t1做爲一個字符串存在於logic階段,可是到物理階段的時候,要從內存或者磁盤上取來真正指向物理資源的一個類型,存儲到計劃裏,以備後用。

例以下面的示例當中,就是一段物理執行計劃,他從上面的Projection存儲的各類字符串,轉換到了存儲表對應的schema,以及RBChunk類型。

pub(crate) struct IOxReadFilterNode<C: PartitionChunk + 'static> {
    table_name: Arc<String>,
    schema: SchemaRef,
    chunk_and_infos: Vec<ChunkInfo<C>>,
    predicate: Predicate,
}

物理執行計劃的優化

對於物理計劃Fasion中提供了3種優化方式,以下圖:

分別是批處理、分區合併、並行度優化。主要是爲了在實際實行的過程當中,減小由於通信、調用、單機等形成的響應緩慢。


在文章的最後展現一下一個物理執行之計劃都包含了哪些信息:

ProjectionExec { 
expr: 
  [ (Column { name: "fieldKey" }, "fieldKey"), (Column { name: "tag1" }, "tag1"), (Column { name: "tag2" }, "tag2"), 
    (Column { name: "time" }, "time")], 
schema: 
  Schema { 
    fields: 
        [Field { name: "fieldKey", data_type: Utf8, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
        Field { name: "tag1", data_type: Dictionary(Int32, Utf8), nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
        Field { name: "tag2", data_type: Dictionary(Int32, Utf8), nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
        Field { name: "time", data_type: Timestamp(Nanosecond, None), nullable: false, dict_id: 0, dict_is_ordered: false, metadata: None }],
    metadata: {} 
  }, 
input: 
  RepartitionExec { 
    input: 
      IOxReadFilterNode { 
        table_name: "myMeasurement", 
        schema: 
          Schema { 
            fields: 
              [Field { name: "fieldKey", data_type: Utf8, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
              Field { name: "tag1", data_type: Dictionary(Int32, Utf8), nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
              Field { name: "tag2", data_type: Dictionary(Int32, Utf8), nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
              Field { name: "time", data_type: Timestamp(Nanosecond, None), nullable: false, dict_id: 0, dict_is_ordered: false, metadata: None }], 
            metadata: {} 
          }, 
        chunk_and_infos: 
        [ChunkInfo { 
        chunk_table_schema: 
          Schema { 
            inner: 
              Schema { 
                fields: 
                  [Field { name: "fieldKey", data_type: Utf8, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
                  Field { name: "tag1", data_type: Dictionary(Int32, Utf8), nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
                  Field { name: "tag2", data_type: Dictionary(Int32, Utf8), nullable: true, dict_id: 0, dict_is_ordered: false, metadata: None }, 
                  Field { name: "time", data_type: Timestamp(Nanosecond, None), nullable: false, dict_id: 0, dict_is_ordered: false, metadata: None }], 
                metadata: 
                  {"tag2": "iox::column_type::tag", 
                  "time": "iox::column_type::timestamp", 
                  "fieldKey": "iox::column_type::field::string", 
                  "tag1": "iox::column_type::tag"} } 
           }, 
           chunk: 
              MutableBuffer { 

                。。。省略MBChunk數據
     }
}

就到這裏,祝玩兒的開心。


歡迎關注微信公衆號:

或添加微信好友: liutaohua001

相關文章
相關標籤/搜索