探索MySQL源代碼–sql歷險記



本文從一個select語句的執行過程出發, 遍歷MySQL的多個几子系統.

先放圖一張, 按圖索驥開始咱們的歷險.

點擊在新窗口查看全圖 CTRL+鼠標滾輪放大或縮小


當客戶端鏈接上MySQL服務端以後,發出請求以前,服務端的線程是阻塞在do_command(sql/parse.cc)裏的my_net_read函數中(就是socket裏的read).

當客戶端鍵入sql語句(本文例子select * from zzz)發送到服務端以後, my_net_read返回, 並從tcpbuffer中讀取數據寫入到packet這個字符串.

html

packet_length= my_net_read(net);


packet的第一個字節是個標誌位, 決定數據包是查詢仍是命令,成功,或者出錯。

接下來就進入dispatch_command(sql/sql/parse.cc)這個函數,數據類型再也不須要.

mysql

return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));


進入dispatch_command, 咱們可見

sql

statistic_increment(thd->status_var.questions, &LOCK_status);


這個就是show status questions的值累加.

接下的mysql_parse(sql/sql_parse.cc), 該函數是sql語句解析的總路口.

進入該函數後首先碰到的是query_cache_send_result_to_client,故名思義這個函數是在QCache裏查詢是否有相同的語句, 有則當即從QCache返回結果, 因而整個sql就結束了.

QCache裏不存在的sql則繼續前進來到parse_sql(sql/sql_parse.cc),這個函數主要就是調用了MYSQLparse.
而MYSQLparse其實就是bison/yacc裏的yyparse(^_^),

socket

#define yyparse         MYSQLparse


是的開始解析sql了. 關於詞法分析和語法匹配簡單說幾下.

對於一條像select * from zzz的語句首先進入詞法分析,找到2個token(select, from),
而後根據token進行語法匹配, 規則在sql/yacc.yy裏,

我把幾個匹配到的pattern和action貼出來.

tcp

select:
          select_init
          {
            LEX *lex= Lex;
            lex->sql_command= SQLCOM_SELECT;
          }
        ;

/* Need select_init2 for subselects. */
select_init:
          SELECT_SYM select_init2
        | '(' select_paren ')' union_opt
        ;

select_paren:
          SELECT_SYM select_part2
          {
            LEX *lex= Lex;
            SELECT_LEX * sel= lex->current_select;
.....

select_from:
          FROM join_table_list where_clause group_clause having_clause
          opt_order_clause opt_limit_clause procedure_clause
          {
            Select->context.table_list=
              Select->context.first_name_resolution_table=
                (TABLE_LIST *) Select->table_list.first;
          }
....
select_item_list:
          select_item_list ',' select_item
        | select_item
        | '*'
          {
            THD *thd= YYTHD;
            Item *item= new (thd->mem_root)
                          Item_field(&thd->lex->current_select->context,
                                     NULL, NULL, "*");
            if (item == NULL)
              MYSQL_YYABORT;
            if (add_item_to_list(thd, item))
              MYSQL_YYABORT;
            (thd->lex->current_select->with_wild)++;
          }
        ;   

select_item:
          remember_name select_item2 remember_end select_alias
          {
            THD *thd= YYTHD;
            DBUG_ASSERT($1 < $3);

            if (add_item_to_list(thd, $2))
              MYSQL_YYABORT;
            if ($4.str)
...


能夠看到action裏最關鍵的就是add_item_to_list 和table_list的賦值.
解析後對於須要查詢的表(zzz)和字段(*)這些信息都寫入到thd->lex這個結構體裏了.

例如其中thd->lex->query_tables就是表(zzz)的情況, thd->lex->current_select->with_wild 是表示該語句是否使用了*等等.

函數

(gdb) p *thd->lex->query_tables
$7 = {next_local = 0x0, next_global = 0x0, prev_global = 0x855a458, db = 0x85a16b8 "test", alias = 0x85a16e0 "zzz",
  table_name = 0x85a16c0 "zzz", schema_table_name = 0x0, option = 0x0, on_expr = 0x0, prep_on_expr = 0x0, cond_equal = 0x0,


sql解析完了, 優化呢? 別急接着往下看.
接着進入mysql_execute_command函數,這個函數是全部sql命令的總入口.

因爲是這個sql是一個select, 因而execute_sqlcom_select就是咱們下個要執行的函數,
又而後進入了mysql_select(^_^怒瞭如此複雜).

mysql_select 就是優化器的模塊, 這個模塊代碼比較複雜. 咱們能夠清楚看到建立優化器,優化,執行的3個步驟, 優化細節不表.

優化

if (!(join= new JOIN(thd, fields, select_options, result)))
...
if ((err= join->optimize()))
...
join->exec();


結束了優化,咱們要具體執行join->exec(), 該函數實際進入的是JOIN::exec()(sql_select.cc).

exec()首先向客戶端發送字段title的函數send_fields, 沒數據但字段也是要的.

而後再進入do_select(), 根據表的存儲引擎跳入到引擎具體的實現(zzz是myisam).
這裏mi_scan(info, buf) 就是myisam引擎掃描文件的函數,再看到info->filename=’./test/zzz’,不就是zzz表對應的物理文件嗎.

經過一系列的mi函數訪問磁盤拿到數據以後,會經過send_data發送數據給client,並從dispatch_command返回.最後在net_end_statement結束整個sql.

一個簡單的select語句背後的執行過程是很是複雜的. 上面的步驟都只是點到就止.

ps: 在sql_yacc.yy可見MySQL對於Oracle中經常使用的dual表的嘲諷.ui

相關文章
相關標籤/搜索