1. 程式人生 > >MySQL 原始碼分析 v1.0

MySQL 原始碼分析 v1.0

第一節.mysql編譯
參考
https://blog.jcole.us/innodb/
https://www.cnblogs.com/zengkefu/p/5674503.html
https://dev.mysql.com/doc/internals/en/getting-source-tree.html
https://www.cnblogs.com/-xep/p/8045213.html
https://www.jianshu.com/p/1cc29d893cfc
https://www.aliyun.com/jiaocheng/357678.html
https://www.cnblogs.com/songxingzhu/p/5713553.html
https://blog.csdn.net/yi247630676/article/details/80352655
https://blog.csdn.net/vipshop_fin_dev/article/details/79688717
http://blog.51cto.com/wangwei007/2300217?source=dra
http://ourmysql.com/archives/1090
http://www.orczhou.com/index.php/2012/11/mysql-innodb-source-code-optimization-1/
http://ourmysql.com/archives/1282
http://ourmysql.com/archives/1277
http://ourmysql.com/archives/1305
<深入理解mysql> 
<flex與bison>
https://dev.mysql.com/doc/internals/en/selects.html

cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DMYSQL_UNIX_ADDR=/tmp/mysql.sock -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_ARCHIVE_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DENABLED_LOCAL_INFILE=1 -DMYSQL_USER=_mysql -DMYSQL_TCP_PORT=3306 -DMYSQL_DATADIR=/data/mysql -DDOWNLOAD_BOOST=1 -DWITH_BOOST=/Users/feivirus/Documents/software/boost

./mysqld --initialize-insecure --user=mysql --datadir=/data/mysql --explicit_defaults_for_timestamp=true

在/usr/local/mysql/bin/下面
sudo ./mysqladmin -uroot -pxxx shutdown 
在/usr/local/mysql/support-files/mysql.server下
sudo ./mysql.server start

第二節.mysql原始碼模組
一.mysql原始碼結構
(一)mysql-server根目錄下:
1.client mysql命令列客戶端工具
2.dbug 除錯工具
3.Docs 一些說明文件
4.include 基本的標頭檔案
5.libmysql 建立嵌入式系統的mysql客戶端程式API
6.libmysqld mysql伺服器的核心級API檔案(8.0沒了?)
7.mysql-test mysql的測試工具箱
8.mysys 作業系統API的大部分封裝函式和各種輔助函式
9.regex 處理正則表示式的庫
10.scripts 一些基於shell指令碼的工具
11.sql 主要原始碼
12.sql-bench 一些效能測試工具(8.0沒了)
13.ssl一些ssl的工具和定義(8.0改為mysys_ssl) 
14.storage 外掛式儲存引起的程式碼
15.strings 各種字串處理函式
16.support-files 各種輔助檔案
17.vio 網路層和套接層的程式碼
18.zlib 資料壓縮工具(8.0移到了utilities)

二.mysql啟動
(一)mysql main()啟動過程
(二)mysql select查詢過程
如下圖斷點:

mysql 查詢

依次需要經過連線處理,sql語法解析,sql優化,sql執行(儲存引擎)四層處理.需要經過選取(限制),投影,連線處理.
依次經過的Sql介面(handle_connection()->do_command()->dispatch_command()),查詢解析器(mysql_parse()->parse_sql()->yyparse()->mysql_execute_command()(為優化做準備)->handle_select()),查詢優化器(join.prepare()->join.optimize()->join.exec()),查詢執行器(do_select()->sub_select()->join.result.send_eof())

從棧底到棧底的每個方法階段的功能簡要描述:
sql連線介面階段:
1.在mysqld.cc檔案中,mysql啟動後,進入mysqld_main().在該main()方法中進入setup_conn_event_handler_threads().通過socket_conn_event_handler()進入socket_conn_event_handler(),在該方法中死迴圈呼叫connection_event_loop(),進入Connection_handler_manager::process_new_connection(),進入add_connection(). 在檔案connection_hanlder_per_thread.cc中通過Per_thread_connection_handler::add_connection()方法,呼叫mysql_thread_create建立執行緒,執行緒的執行函式為connection_handler_per_thread.cc檔案中的handle_connection().
2.在handle_connection中,通過while迴圈呼叫sql_parse.cc中的do_command(),迴圈的作用是從使用者輸入的多個命令中依次選擇一個命令去執行.在do_command()中,呼叫Protocol_classic::get_command(),進入Protocol_classic::parse_packet()讀取一個數據包,解析為command命令,command命令有COM_QUERY、COM_FIELD_LIST、COM_STMT_EXECUTE等。在在do_command()中繼續呼叫sql_parse()檔案中的dispatch_command().在dispatch_command()中包含了各種型別的command怎麼處理的switch case。比如select的進入case COM_QUERY.先alloc_query()複製執行緒執行緒過來的查詢命令。再進入sql_parse.cc檔案中的mysql_parse()方法,開始sql語法解析.

sql語法解析階段:
3.在mysql_parse()中,主要完成三個工作。檢查查詢快取裡是否有以前執行過的某個查詢。呼叫詞法解析器(通過parse_sql()方法),呼叫查詢優化器(通過mysql_execute_command()方法).
4.依次進入sql_parse.cc中的parse_sql(),進入sql_class.cc中的THD::sql_parser(),進入MYSQLparse().MYSQLparse是個巨集定義,為#define yyparse MYSQLparse.這時進入yacc的sql_yacc.cc檔案中的語法解析器函式yyparse()開始語法解析.解析器基於sql_yacc.yy檔案通過yacc生成.Mysql的詞法解析器沒有用flex,自己編寫的。語法解析器用的Bison.
5.在mysql_parse()中繼續呼叫sql_parse()檔案中的mysql_execute_command()方法,為sql優化做準備.在該方法中,通過switch case為各種sql命令優化做準備.如果是select查詢,進入case SQLCOM_SELECT,進入Sql_cmd_dml::execute()方法.在execute()方法中,先呼叫precheck()查詢是否有許可權,然後呼叫open_tables_for_query()開啟要查詢的表,然後呼叫lock_tables()鎖定相關的表,然後呼叫Sql_cmd_dml::execute_inner(),進入真正的查詢優化.

sql查詢優化階段:
在Sql_cmd_dml::execute_inner()方法中,先進入sql_optimizer.cc檔案中的JOIN::optimize(),再進入sql_executor.cc檔案中的JOIN::exec()開始查詢執行器。

sql查詢執行器:
在sql_executor.cc檔案中的JOIN::exec()中.先呼叫send_result_set_metadata()生成查詢標題,再進入sql_executor.cc中的do_select()生成查詢結果.在do_select()中呼叫sub_select().在sub_select()中,先判斷是否是最後一條記錄.然後while迴圈每次讀取一條記錄。

第三節.儲存引擎,函式,命令
一.關鍵類
sql/handler.h下的struct handlerton,class handler,比如選擇inno引擎,create table時進入storage/innobase/handler/ha_innobase::create()方法.insert into語句時進入ha_innobase::write_row().
二.使用者自定義函式
呼叫命令create function,實現函式的so庫。參考例子在sql/udf_example.cc中。
三.本機函式
修改程式碼在lex.h,item_create.cc,item_str_func.cc中。常用函式都在lex.h中。
四.新的sql命令
修改sql_yacc.yy,sql_parse.cc.所有系統支援的命令在enum_sql_command列舉類中。命令處理在sql_parse.cc的mysql_execute_command()中。

第四節.SQL介面

第五節.查詢解析器(Lex-YACC/Bison)
一.詞法分析
在sql_yacc.cc檔案中定義
#define yyparse MYSQLparse
#define yylex   MYSQLlex
#define yyerror MYSQLerror
#define yylval  MYSQLlval
所以詞法分析在sql_lex.cc的MYSQLlex()方法中.呼叫堆疊如圖

進入lex_one_token(),在find_keyword()中呼叫Lex_hash::get_hash_symbol()查詢關鍵字.關鍵字是根據lex.h中的symbols[]陣列呼叫gen_lex_hash.cc生成lex_hash.h中的符號陣列sql_keywords_and_funcs_map[].詞法分析解析到SELECT後,執行find_keyword去找是否是關鍵字,發現SELECT是關鍵字,於是給yacc返回SELECT_SYM用於語法分析。note:如果我們想要加關鍵字,只需在sql_yacc.yy上面新增一個%token xxx,
然後在lex.h裡面加入相應的字串和SYM的對應即可。依次取出token,比如sql語句為"select * from user",token依次為select,*,from,user,返回的為token的id號.*的id號
為42.這個id號在sql_yacc.h中定義,比如#define SELECT_SYM 748.已經分析和沒有分析過的語句都放在Lex_input_stream結構的lip變數中.在lex_one_token()依次判斷讀入的符號,解析狀態,比如"select @@version_comment limit 1;"語句,進入case MY_LEX_SYSTEM_VAR狀態,解析MY_LEX_IDENT_OR_KEYWORD,就是"version_comment".


二.語法分析
入口為sql_yacc.cc檔案中的yyparse()方法.
語法分析檔案在sql_yacc.yy中.以"select @@version_comment"為例。進入lex_one_token()方法中的case MY_LEX_SYSTEM_VAR後.
(一).Item物件
可以是一個文字字串/數值物件,表的某一列(例如,select c1,c2 from dual…中的c1,c2),一個比較動作,例如c1>10,一個WHERE子句的所有資訊.
(二).mysql語法樹處理過程

在sql_yacc.yy中,如果是select查詢語句,進入query處理邏輯。具體格式如下
query:

| verb_clause END_OF_INPUT
          {
            /* Single query, not terminated. */
            YYLIP->found_semicolon= NULL;
          }
        ;

select:
          select_init
          {
            LEX *lex= Lex;
            lex->sql_command= SQLCOM_SELECT;
          }
        ;
如果是where語句的,處理如下
where_clause:
          /* empty */  { Select->where= 0; }
        | WHERE
          {
            Select->parsing_place= IN_WHERE;
          }
          expr
          {
            SELECT_LEX *select= Select;
            select->where= $3;
            select->parsing_place= NO_MATTER;
            if ($3)
              $3->top_level_item();
          }
        ;
以select * from users where id = 1 語句為例,

生成的語法樹大致如下左圖,右圖為mysql內部的儲存結構.

以select * from user where id = 1為例,每次讀入一個token,要考慮移進還是規約.通過yylex()方法得到的yychar是id號,該id號數字在sql_yacc.h中定義.
1.lex_one_token,讀入"select"->select_sym token->規約到sql_yacc.yy 的select_part2:

2.讀入*號,依次規約到sql_parse.yy中的select_item_list:

3.讀入from,依次規約到sql_parse.yy中的select_part2:

4.讀入"user",依次規約到table_factor:->keyword_sp:->keyword:->ident:

5.讀入"where",依次規約到simple_ident_q:->opt_use_partition:->opt_table_alias:->opt_key_definition:->table_factor:->

join_table:->esc_table_ref:->derived_table_list:->join_table_list:->where_clause:->IDENT_sys

6.讀入"=" 

。。。
sql語法樹的狀態切換過程如下,數字為狀態轉換表的編號,編號順序為上面左圖從樹葉到樹根的語法解析過程,括號內為bison規約的mysql中的非終結符:
1127(select_part2),1137,讀入*,1149(select_item_list),讀入from,1128(select_part2),讀入user,1444(table_factor),2326(keyword_sp),1988(keyword,只是匹配到,沒有動作),1979(ident),讀入where,1966(simple_ident_q),1441(opt_use_partition),1518(opt_table_alias),1480(opt_key_definition),1477,1481,1445(table_factor),1421(join_table),1412,1415(esc_table_ref),1417(derived_table_list),1414(join_table_list),
1523(where_clause),1973(IDENT_sys),讀入=號,1978(ident),1955(simple_ident),1223,1206,1191,1177,1215(comp_op),讀入數字1,1942(NUM_literal),1933(literal),1229,讀入end_of_input,1206,1191,1175(,建立eq_creator),1171,1524(where_clause),1530,1525,1541,1552(opt_limit_clause),1588,1135(select_from),1132,1144,1129,1125(select_init2),2548(union_clause),1126,1119,1118(select),50,8,5(query).

(三).Mysql語法樹儲存結構

以select * from user where id = 1為例,如上圖右側圖所示.Mysql語句中的*為投影的列,儲存在thd->main_lex.current_select->item_list中,列表中的每一項為一個list_node. where條件儲存在thd->main_lex.current_select->where物件中。where為級聯結構.

處理過程如下,比如處理到"="時的,堆疊如下:

mysql為"="建立一個Eq_creator類標識,如前面圖所示,它儲存在thd->main_lex.current_select->where物件中.where型別為

item_func_eq類.它依次繼承Item_bool_rowready_func2,Item_bool_fun2,如下圖所示

在Item_bool_fun2類中有cmp成員,型別為Arg_comparator,如下圖

儲存了=運算子的兩邊的表示式.

 

第六節.查詢優化器
一.索引
表的索引儲存在 KEY *TABLE::key_info陣列中.KEY在class KEY中定義,其中的KEY_PART_INFO *key_part指向每一個索引列。索引種類儲存在my_base.h中,比如#define HA_FULLTEXT 128.
入口JOIN::optimize()

第七節。查詢執行處理器

第八節。快取和緩衝區

第九節。高效能索引.

第十節.主從複製

第十一節.備份與恢復