MySQL 源碼分析 v2.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
https://www.cs.usfca.edu/~galles/visualization/BTree.html
https://blog.csdn.net/u012935160/column/info/innodb-zerok
http://hedengcheng.com/?p=293
https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html#auto_id_12
https://www.cnblogs.com/xpchild/tag/MySQL/
https://blog.csdn.net/bohu83/column/info/25114
https://bbs.huaweicloud.com/blogs/4fddbe400f1c11e89fc57ca23e93a89f
https://www.cnblogs.com/shijingxiang/articles/4743324.html
http://mysqlserverteam.com/re-factoring-some-internals-of-prepared-statements-in-5-7/

 

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

(二).帶着問題看源碼

1.爲什麼where column_name is null 不走索引,is not null走索引
2.一個頁內四條記錄,id爲1,2,3,4,如果2和3刪除了,各2k,現在有個3k的進來,是裂變,加新頁嗎?不整理內存碎片?
3.索引文件的格式,內存裏面和文件裏面是否一致,非葉節點的存儲格式,blog,text字段長的存儲格式也是存儲在不同的頁?
4.explain中的rows和filtered rows數字怎麼來的
5.in和or性能上有區別嗎?
6.頁面內部插入記錄,插入10,20,再插入15,頁面內部順序是如何處理的,20向後?鏈表處理?內存碎片如何處理
7.mysql對sql語句優化了哪些內容?
8.group by找到多條記錄之後,是怎麼選擇一條記錄出來的?隨機還是有排序?
9.sql優化時的代價估算,具體怎麼估算?
10.innodb的定時任務去刷新磁盤,怎麼刷新的?
11.limit 1000,100爲什麼要查出1100條數據,再選出100條出來?不直接選擇100條數據
12.select * from t where a=x and b=x order by c. 如果你的表有1000w行 你怎麼建索引優化這個sql
13.如何判斷sql單表查詢用哪個索引,索引的界限,條件,索引下推的過程.區分unique key,主鍵,普通的key,無索引
14.單表查詢在使用的索引上加什麼鎖,鎖哪些記錄,會不會死鎖
15.多表join過程
16.limit 10000,10時,如果用索引,會不會先順序查詢10000條,再取10條.加上 order by呢.
17.mvcc下,執行update,delete的過程
18.單表下,執行計劃代價cost是怎麼估算的.

 

第二節.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,如下圖

存儲了=運算符的兩邊的表達式.


第六節 查詢優化器


一.優化器架構
分爲子查詢優化,視圖重寫,謂詞重寫,條件化簡,外連接消除,嵌套連接消除,語義優化,多表連接優化等。

(一).優化準備階段JOIN::prepare()

主要處理子查詢的冗餘子句,優化IN,ANY,ALL,EXISTS命令.


(二).優化入口JOIN::optimize().
依次調用1.simplify_joins(),做連接消除,把外連接轉換爲內連接,比如兩個表通過外鍵關聯,沒有null行,一一對應.
2.然後調用optimize_cond()做條件優化,優化where字句,比如常量傳播,比如where a=b and b =40,如果a上有索引,優化成 a= 40 and b =40,可以走索引.
3.調用optimize_cond()做having中的條件優化.
4.調用optimize_fts_limit_query做單表查詢中,沒有where子句,有一個order by子句,有一個limit 子句,
5.調用opt_sum_query做count(*),min(),max()優化.替換其中的常量.
6.調用make_cond_for_table()
7.調用make_join_statistics()確定多表連接的順序,策略.
8.調用make_outerjoin_info()填充連接外鍵信息
9.調用substitue_for_best_equal_field()優化等值表達式,去掉重複的等式.
10.調用make_join_select()確定join的連接方式,使用哪些索引.具體見下面的"三.多表連接優化".
11.調用order_with_src()優化distinct語句

二.子查詢優化
(一).exits優化
測試語句 select * from user u1 where exists(select * from user u2 where u2.id=1);
會優化成 select * from user u1 where 1;
入口optimze_cond(),進入internal_remove_eq_conds.進入eval_const_cond先執行u2.id=1的子查詢.進入Item_exists_subselect::val_init()進入subselect_single_select_engine::exec()執行子查詢.又進入JOIN::optimize().執行完子查詢後exists返回true,

 

三.多表連接優化

入口是make_join_statistics().主要邏輯在方法上有清晰的註釋.
(一).for循環初始化每個表連接用的數據結構
(二).如果是外連接,分析表之間的依賴關係
(三).根據依賴關係做半連接上拉(pull out semi-join tables)操作.
(四).確定連接的外表和內表之間有沒有相關關係
(五).如果是標量子查詢,做查詢,得到標量子查詢的數據.
(六).計算每個表需要匹配的行數
(七).如果可以使用索引,計算where 條件的區間內返回的條數.
(八).判斷是否有impossibel where 或者on
(九).調用Optimize_table_order().choose_table_order()確定連接策略.
1.判讀是否是straight_join().
2.調用greedy_search()做貪婪算法判斷.
3.調用fix_semijon_strategies()確定半連接策略
(十).調用get_best_combination()從確定好的連接策略中創建執行計劃.

四.謂詞優化

(一).not優化

(二).or 優化

(三). any 優化

(四).count() 聚集函數優化

第七節 查詢執行處理器


一.執行處理器架構
入口在sql_executor.cc文件中的JOIN::exec()中.先調用send_result_set_metadata()生成查詢標題,再進入sql_executor.cc中的do_select()生成查詢結果.在do_select()中調用first_select(),進入sub_select().在sub_select()中
1.先判斷是否是最後一條記錄,主要用於嵌套查詢或者join時的子查詢中遞歸返回.在這個判斷進入next_select()時是嵌套或者join的最後一層.
2.調用read_first_record時,如果是第一次查詢該表,進入records.cc文件中的init_read_record()創建讀表的READ_RECORD結構,初始化要讀取的表的信息,判斷是走索引還是普通全部掃描. 調用handler::ha_rnd_init爲掃描做innodb的初始化準備.進入ha_innobase::rnd_init(),後續詳情見後面"innodb引擎的初始化".在init_read_record中繼續調用handler::extra_opt做一些額外處理.比如檢查事務是否存在.然後調用handler::ha_rnd_next掃描記錄.進入rr_sequential,開始innodb掃描記錄.
3.在sub_select()中繼續通過while循環遍歷一條條記錄,調用read_record()讀取記錄.每次讀取一條記錄後,在sub_select的while循環內進入evaluate_join_record()做where 後面的條件判斷,如下圖

4.在evaluate_join_record中先取出當前表相關的where的條件,判斷取出的記錄是否滿足where條件.判斷是否滿足是進入每種判斷條件的Item::val_int()方法.每一種判斷條件都繼承Item類.如果當前記錄滿足where條件,進入JOIN_TAB::next_select()方法.在這裏進入下一層left join 的sub_select(),開始下一層join的判斷。如果當前是最後一層join,就發送滿足條件的結果給客戶端.

基本的join過程總結如下:
比如 SELECT * from a  
left join b 
on a.id = b.a_id
left join c 
on c.a_id = a.id 
where c.phone like '13%' and b.money>20 and a.total > 20;
sql優化後,執行如下:
for(id in b) {
     取出第一條記錄爲id=14215,對應的a_id爲74417
     判斷是否滿足b.money>20 .滿足則進入下面for

      for(a_id in a) {
         取出a_id爲74417的記錄,假設只有一條
         判斷是否滿足a.total > 20, 滿足則進入下面for

          for(a_id in c) {
                 取出a_in爲74417的記錄,有三條,for 循環執行三次
                 判斷是否滿足 c.phone like '13%'
                 輸出記錄
          }
      }
}

 

第八節 innodb引擎

 

一.innodb的存儲結構
(一).表空間
innodb的的表空間(tablespace)分爲段(segment),區(extent),頁(page),頁目錄內的槽(slot),行組成.InnoDB有一個共享表空間ibdata1,如果啓用innodb_file_per_table,則每張表數據單獨放一個表空間內,undo信息,系統事務信息,二次寫緩衝還是放在共享表空間內.一個表空間即一個B+樹,分爲兩個段,數據段(葉節點),索引段(內節點).區由64個連續的頁組成,每個頁大小爲16KB,每個區就是1MB.每個段開始時,先有32個碎片頁,碎片頁用完後再每次以區爲單位申請,即64個連續頁。初始化表空間內有4個頁,按照頁號從0到3分別是File Space Header頁,Insert Buffer Bitmap頁,File Segment inode頁,page level爲0(代表葉節點,只有一頁,葉節點就是根節點).外帶兩個空閒頁.如圖


(二).段
(三).區
(四).頁
頁類型有數據頁(B-tree Node),Undo頁,系統頁,事務數據頁,插入緩衝位圖頁,插入緩衝空閒列表頁,Uncompressed BLOG Page,Compressed BLOG Page等.
頁由File Header,Page Header, Infimun + Supremum Records,User Records(用戶數據),Free Space,Page Directory,File Trailer組成.如下圖


1.File Header.
佔用38個字節.存放比如checksum,表空間的頁偏移,上一頁的偏移,最後被修改的日誌的LSN,頁類型.
2.Page Header.
記錄頁的狀態信息,共56個字節,存放頁目錄槽數,堆中第一個可用空間的指針(page_heap_top,對於沒有插入數據記錄的頁面來說,38+56+13+13=120字節處,長度爲2,偏移爲4,可用空間的最小位置),堆中記錄數等,空閒鏈表的指針,已刪除記錄的字節數,最後插入方向,page_level(0代表葉節點),文件段的首指針.
3.Infimun + Supremum記錄.
Infimun存儲記錄比頁內鍵值都小的值,Supremum是存儲大的值,用於限定記錄邊界.各佔13個字節.
4.Page Directory
5.File Trailer 8個字節的FIL_PAGE_END_LSN,前四個字節是校驗和,後四個字節和File_Header中的checksum相同,保證頁的完整性.

(五).行
行分爲Compact和Redundant兩種存儲格式.Compact格式如下圖:


變長字段長度列表 | NULL標誌位 | 記錄頭信息 | 列1數據 | 列N數據...
列長小於255字節,用1個字節表示。大於255個字節,用兩個字節表示,兩個字節16位,2的16次方爲65535,即varchar的最大長度.NULL標誌位表示行中是否有NULL值,佔1個字節.記錄頭信息佔用5個字節,表示是否刪除,記錄數,記錄類型,16位的下一記錄的相對偏移.6個字節的事務id列,7個字節的回滾指針列,如果沒有主鍵,還有6個字節的RowID列.後面跟着行的具體數據.如果該行數據溢出,則新創建BLOB頁存儲,本行只記錄前768字節,後面跟着行溢出頁的偏移.


二.引擎初始化
入口爲ha_innodb.cc文件中的ha_innobase::rnd_init().進入change_active_index()修改掃描主鍵.

三.掃描記錄
入口handler::ha_rnd_next().進入pfs.cc文件中的start_table_io_wait_v1()方法.進入ha_innod.cc文件中的rnd_next()方法讀取記錄.
1.先調用ha_statistic_increment做統計分析.
2.判斷是否是讀取第一條記錄,如果是第一條進入index_first()。不是第一條記錄進入general_fetch()方法.在general_fetch()方法中先進入srv0conc.cc文件中的srv_conc_enter_innodb()方法中創建io線程,處理事務.然後調用row_search_for_mysql()讀取記錄.入參是個buf指針.
3.在row_search_for_mysql()方法中,讀索引,判斷是否是compact壓縮格式,讀取事務id,確定頁面offset.判斷索引是否損壞等.獲取一個s-latch鎖.判斷讀取inc還是desc方向.判斷是否是聚集索引,匹配模式是否是ROW_SEL_EXACT.調用mtr_start開始事務.判斷是否要恢復索引位置.跳到next_rec標記處,進入btr_pcur_move_to_next()方法,在該方法中判斷是不是頁面中用戶記錄的最後一條,如果是則返回。不是最後一條,進入btr_pcur_move_to_next_on_page()->page_cur_move_to_next移動遊標到頁面的下一條記錄.進入page0page.ic文件中的page_rec_get_next()獲取頁內下一條記錄的offset,在row_search_for_mysql()中調用btr_pcur_get_rec()根據遊標獲取記錄.在row_search_for_mysql繼續調用rec_get_next_offs獲取下一條記錄的偏移.在row_search_for_mysql中經過一系列調用,然後調用row_sel_store_mysql_rec把innodb的row格式轉換成mysql得到record格式.進入row0sel.cc文件中的row_sel_store_mysql_field_func()方法.進入row_sel_field_store_in_mysql_format_func()方法.for循環遍歷所有列字段,根據字段是int,varchar等轉換字段格式.在row_search_for_mysql()中繼續調用mtr_commit()提交事務.
4.頁內通過槽搜索記錄代碼在page_cur_search_with_match()中.

四.讀取表
在使用命令"use test"切換數據庫時,進入sql_base.cc文件中的open_normal_and_derived_tables()方法.如下圖

依次進入open_tables()->open_table_from_share(),進入handler.cc文件中的handler::ha_open()方法,依次進入ha_innobase::open()->dict0dict.cc文件中的dict_table_open_on_name()->dict_load_table()處理如下:
1.獲取"sys_tables"系統表,獲取系統表的索引,獲取系統表的ID,N_COLS,TYPE,MIX_LEN,SPACE列.調用btr_open_on_user_rec()讀取表的遊標。進入btr_pcur_init()初始化遊標.
2.進入dict_load_table_low載入表.從sys_tables中獲取user表的信息.調用dict_mem_table_create()創建user表的表空間.
3.在dict_load_table中,繼續調用innobase_format_name轉換表名爲mysql的格式.調用fil_space_for_table_exists_in_mem()填充table space.

五.內存管理
(一).頁內插入記錄,申請空間調用方法page0page.cc文件中的page_mem_allc_heap()方法.具體原理參考上面innodb引擎介紹的頁一節.
(二).頁面碎片管理
新建buffer pool頁面,將碎片頁面的數據插入新頁面,記錄從小到大複製過去.原來的buffer pool頁面釋放掉.
(三).索引頁面的回收
(四).頁面內記錄刪除.檢查當前頁面剩餘記錄數,如果只剩下一條,就從B+樹中釋放,回收頁面.


六.插入記錄
入口是ha_innobase::write_row().首先把接收到的record記錄轉爲內部的tuple,索引元組格式,最後轉成row格式.

1.進入innobase_srv_conc_enter_innodb()開始事務.進入row_insert_for_mysql()->row_ins_step()->row_ins()->row_ins_index_entry_step()->row_ins_index_entry()->row_ins_clust_index_entry()->row_ins_clust_index_entry_low->btr_cur_optimistic_insert->進入page_cur_tuple_insert()方法.如下圖:

page_cur_tuple_insert()入參是tuple類型.首先計算記錄空間大小(包括兩部分,extra_size,數據內容),統計每一個列中數據的長度.進入rec_convert_dtuple_to_rec_new()方法轉換記錄.在這個方法中,記錄頭大小,調用rec_convert_dtuple_to_rec_comp轉爲compact格式記錄.在這個方法中填充row中的長度,數據信息.

2.在page_cur_tuple_insert()方法中調用page0cur.cc文件中的page_cur_insert_rec_low()在頁面中插入記錄.在該方法中,首先獲取頁面中記錄大小,然後調用page_header_get_ptr()找到頁面可用空間中合適的位置,然後調用上面"五內存管理之頁面插入記錄"中提及的page_mem_allc_heap()方法找到頁內可以用的heap空間.調用rec_copy()方法把記錄拷貝到heap中.然後修改頁面記錄鏈表的指針.然後修改page header中的最後插入位置.最後調用page_cur_insert_rec_write_log()寫插入日誌.

3.依靠Buffer Pool的刷新磁盤機制刷新到磁盤.

第九節 Buffer Pool

第十節 主從複製

第十一節. redolog與undolog

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章