程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> MySQL綜合教程 >> 探索MySQL源代碼之SQL歷險記

探索MySQL源代碼之SQL歷險記

編輯:MySQL綜合教程

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

先放圖一張, 按圖索骥開始我們的歷險.

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

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

  1. packet_length= my_net_read(net); 

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

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

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

進入dispatch_command, 我們可見

  1. 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(^_^),

  1. #define yyparse MYSQLparse 

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

對於一條像select * from zzz的語句首先進入詞法分析,找到2個token(select, from), 然後根據token進行語法匹配, 規則在sql/yacc.yy裡, 我把幾個匹配到的pattern和action貼出來.

  1. select: 
  2. select_init 
  3. LEX *lex= Lex; 
  4. lex->sql_command= SQLCOM_SELECT; 
  5. /* Need select_init2 for subselects. */ 
  6. select_init: 
  7. SELECT_SYM select_init2 
  8. | '(' select_paren ')' union_opt 
  9. select_paren: 
  10. SELECT_SYM select_part2 
  11. LEX *lex= Lex; 
  12. SELECT_LEX * sel= lex->current_select; 
  13. ..... 
  14. select_from: 
  15. FROM join_table_list where_clause group_clause having_clause 
  16. opt_order_clause opt_limit_clause procedure_clause 
  17. Select->context.table_list= 
  18. Select->context.first_name_resolution_table= 
  19. (TABLE_LIST *) Select->table_list.first; 
  20. .... 
  21. select_item_list: 
  22. select_item_list ',' select_item 
  23. | select_item 
  24. | '*' 
  25. THD *thd= YYTHD; 
  26. Item *item= new (thd->mem_root) 
  27. Item_field(&thd->lex->current_select->context, 
  28. NULL, NULL, "*"); 
  29. if (item == NULL) 
  30. MYSQL_YYABORT; 
  31. if (add_item_to_list(thd, item)) 
  32. MYSQL_YYABORT; 
  33. (thd->lex->current_select->with_wild)++; 
  34. select_item: 
  35. remember_name select_item2 remember_end select_alias 
  36. THD *thd= YYTHD; 
  37. DBUG_ASSERT($1 < $3); 
  38. if (add_item_to_list(thd, $2)) 
  39. MYSQL_YYABORT; 
  40. if ($4.str) 
  41. ... 

可以看到action裡最關鍵的就是add_item_to_list 和table_list的賦值.

解析後對於需要查詢的表(zzz)和字段(*)這些信息都寫入到thd->lex這個結構體裡了.

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

  1. (gdb) p *thd->lex->query_tables 
  2. $7 = {next_local = 0x0, next_global = 0x0, prev_global = 0x855a458, db = 0x85a16b8 "test", alias = 0x85a16e0 "zzz", 
  3. 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個步驟, 優化細節不表.

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

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

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

然後再進入do_select(),根據表的存儲引擎跳入到引擎具體的實現(zzz是myisam表)。

這裡mi_scan就是myisam引擎掃描文件的函數,再看到

  1. (gdb) p info->filename 
  2. ./test/zzz 

這不就是zzz表對應的物理文件嗎。

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

一個簡單的select語句背後的執行過程是非常復雜的,上面的步驟都只是點到就止。

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved