MySQL原始碼:從SQL語句到MySQL內部物件

orczhou發表於2016-03-28

0 寫在前面

本文解決了什麼問題:希望通過這些文章能夠幫你更加順暢的理解MySQL優化器的行為;在你閱讀MySQL原始碼之前瞭解更多的背後思路。

本文不解決什麼問題:教你如何讀懂原始碼;

這個系列很長,大概按這樣的思路進行下去: 基本的資料結構、語法解析、JOIN的主要演算法、JOIN順序和單表訪問。資料結構(以及他們的關係)和演算法流程總是相互穿插介紹。

建議閱讀:參考文獻中的文章和書籍,都建議在閱讀本文之前閱讀。

1 SQL語句解析基礎

1.1 語法解析基礎/Flex與Bison

MySQL語法解析封裝在函式MYSQLparser中完成。跟其他的語法解析器一樣,它包含兩個模組:詞法分析(Lexical scanner)和語法規則(Grammar rule module)。詞法分析將整個SQL語句打碎成一個個單詞(Token),而語法規則模組則根據MySQL定義的語法規則生成對應的資料結構,並儲存在物件THD->LEX結構當中。最後優化器,根據這裡的資料,生成執行計劃,再呼叫儲存引擎介面執行。

詞法分析和語法規則模組有兩個較成熟的開源工具Flex和Bison分別用來解決這兩個問題。MySQL處於效能和靈活考慮,選擇了自己完成詞法解析部分,語法規則部分使用Bison。詞法解析和Bison溝通的核心函式是由詞法解析器提供的函式介面yylex(),在Bison中,必要的時候呼叫yylex()獲得詞法解析的資料,完成自己的語法解析。Bison的入口時yyparse(),在MySQL中是,MYSQLParse。

如果對詞法分析和語法規則模組感到陌生,建議閱讀參考文獻[4][5][6]先注1,否則很難理解整個架構,或者至少會有很強的斷層感。而且,根據Bison的Action追蹤MySQL資料的儲存結構是很有效的。

1.2 MySQL語法解析Sample與示意圖

簡單的解析過程可以使用下面的示意圖說明:

MySQL語法解析說明--1

具體的解析一個SQL語句的WHERE部分:

MySQL語法解析說明--2

2 SQL語句到MySQL的內部物件

Bison在做語法解析後,會將解析結果(一顆解析樹/AST)儲存在THD::LEX中。這裡將通過考察儲存WHERE的資料結構來檢視語法解析的結果。

2.1 著名的Item物件

在瞭解MySQL的解析樹之前,我們需要先來認識一個重要的資料結構Item。這是一個基礎物件,在優化器部分程式碼,滿地都是。在MySQL Internal Manual中也單獨介紹:The Item Class

Item是一個基礎類,在他的基礎上派生了很多子孫。這些子類基本描述所有SQL語句中的物件,他們包括:

一個文字字串/數值物件
一個資料表的某一列(例如,select c1,c2 from dual…中的c1,c2)
一個比較動作,例如c1>10
一個WHERE子句的所有資訊
……

可以看到,Item基本上程式碼SQL語句中的所有物件。在語法解析樹中,這些Item以一顆樹的形式存在。示意圖如下:

WHERE語法樹

2.2 Bison語法中的WHERE

從SELECT子句開始,我們看到對應的where_clause就是我們關注的WHERE:

bison_where

我們來看看Bison中的幾個重要的Action參考注1

where_clause:
        /* empty */ {}
      | WHERE expr
      {
        THD->lex->current_select->where = $2
      }

expr:
      ...
      | expr and expr 
       {
         $$ = new (YYTHD->mem_root) Item_cond_and($1, $3)
       } 
      |ident comp_op NUM   /*這一行並不是原始碼的一部分,便於理解簡化如此*/
      {
         $$ = new Item_func_ge(a, b); /*這一行並不是原始碼的一部分,便於理解簡化如此*/
      }

根據這裡的Bison語法,就可以生產上面的WHERE語法樹了。如果你是和我一樣剛剛瞭解Flex/Bison/AST,一定也會決定很巧妙!

2.3 WHERE的資料結構和他們之間的關係

繪製了下面的關係圖用來描述WHERE和WHERE解析樹的各個分支:

theclassofwhere

例如WHERE條件WHERE c1=”orczhou” and c2 > 10,WHERE本身(lex->select->where)就是一個Item_cond_and物件,這個物件中有一個Item List,將List中每一個Item的值做AND運輸,也就是這個WHERE的取值了。

這裡,WHERE的List中有兩個Item物件,分別代表了c1=”orczhou”和c2 > 10。具體的,這兩個物件的型別分別是Item_func_eq和Item_func_gt。

再單獨看看Item_func_gt(代表c2 > 10)物件,這個物件由Item_func派生而來(當然追根朔源都是Item的孩兒們),這個物件有成員:Item **args。args則存放了比較操作需要使用的Item。

對於c2 > 10,這個不等式中有兩個Item,分別代表欄位c2和整數10,儲存這兩個物件的型別分別是:Item_field和Item_int。

2.4 通過GDB列印WHERE物件

WHERE條件是:WHERE id = 531389273 AND reg_date > `2012-02-12 09`;

列印WHERE中的List


(gdb) p ((Item_cond *)select_lex->where)->list
$13 = {
  <base_list> = {
    <Sql_alloc> = {<No data fields>}, 
    members of base_list: 
    first = 0x7f5bbc005860, 
    last = 0x7f5bbc005870, 
    elements = 2
  }

因為WHERE有兩個判斷,所以這裡list中有兩個元素。

列印list中的第一個判斷(id = 531389273)


(gdb) p *(Item_func *)((Item_cond *)select_lex->where)->list->first->info
$69 = {
  <Item_result_field> = {
    <Item> = {
      ......
      next = 0x7f2134005320, 
      ......
    }, 
    ......
  }, 
  members of Item_func: 
  args = 0x7f2134005420, 
  tmp_arg = {0x7f2134005228, 0x7f2134005320},
  arg_count = 2, 
  .......
}

這裡等於操作有兩個操作元素(arg_count=2),並以陣列的形式儲存在args中

列印上面等式的第一個物件(也就是id)


列印第一個Item的型別
p ((Item_func *)((Item_cond *)select_lex->where)->list->first->info)->args[0]->type()
$74 = Item::FIELD_ITEM
將第一個Item轉換成正確的型別再列印
p *(Item_field *)((Item_func *)((Item_cond *)select_lex->where)->list->first->info)->args[0]
$78 = {
  <Item_ident> = {
    <Item> = {
      .......
      name = 0x7f2134005208 "id", 
      ......
    }, 
    ......
    members of Item_ident: 
    orig_field_name = 0x7f2134005208 "id", 
    field_name = 0x7f2134005208 "id",
    ....... 
  }, 
  members of Item_field: 
  field = 0x0, 
  result_field = 0x0,
  ....... 
}

可以看到這裡的id物件的型別是Item::FIELD_ITEM,也就是Item_field型別。

3 關於Item物件

繼續從儲存WHERE的Item_cond_and物件開始:

classItem__bool__func__inherit__graph

(點選可以檢視大圖)

看到Item_cond_and的繼承關係:Item_cond->Item_bool_func->……->Item_result_filed->Item

Item一個很重要的成員函式就是type,所以在gdb的時候如果不清楚Item的型別,可以呼叫該方法確定:

(gdb) p ((*(Item_func *)thd->lex->current_select->where)->tmp_arg[0])->type()
$42 = Item::FIELD_ITEM

這篇文章就到這吧,希望能夠繼續下去。


相關文章