MySql輕鬆入門系列——第一站 從原始碼角度輕鬆認識mysql整體框架圖

一線碼農發表於2020-06-02

一:背景

1. 講故事

最近看各大技術社群,不管是知乎,掘金,部落格園,csdn基本上看不到有小夥伴分享sqlserver類的文章,看來在國內大環境下是不怎麼流行了,看樣子我再寫sqlserver是不可能再寫了,這輩子都不會寫了,只能靠技術輸出mysql維持生活這樣子。

二:瞭解架構圖

mysql最大的好處就是開源, 手握百萬原始碼,有什麼問題搞不定呢? 這一點要比sqlserver爽多了,不用再dbcc搗來搗去。

1. 從架構圖入手

大家都知道做/裝修房子都要有一張圖紙,其實軟體也是一樣,只要有了這麼一張圖紙,大方向就定下來了,再深入到細節也不會亂了方向,然後給大家看一下我自己畫的架構圖,畫的不對請輕拍。

其實SqlServer,Oracle,MySql架構都大同小異,MySql的鮮明特點就是儲存引擎做成了插拔式,這就牛逼了,現行最常用的是InnoDB,這就讓我有了一個想法,有一套業務準備用 InMemory 模式跑一下,厲害了~~~

2. 功能點介紹

MySql其實就兩大塊,一塊是MySql Server層,一塊就是Storage Engines層。

<1> Client

不同語言的sdk遵守mysql協議就可以與mysqld進行互通。

<2> Connection/Thread Pool

MySql使用C++編寫,Connection是非常寶貴的,在初始化的時候維護一個池。

<3> SqlInterface,Parse,Optimizer,Cache

對sql處理,解析,優化,快取等處理和過濾模組,瞭解瞭解即可。

<4> Storage Engines

負責儲存的模組,官方,第三方,甚至是你自己都可以自定義實現這個資料儲存,這就把生態做起來了,??。

三: 原始碼分析

關於怎麼去下載mysql原始碼,這裡就不說了,大家自己去官網搗鼓搗鼓哈,本系列使用經典的 mysql 5.7.14版本。

1. 瞭解mysql是如何啟動監聽的

手握百萬行原始碼,怎麼找入口函式呢??? ???,其實很簡單,在mysqld程式上生成一個dump檔案,然後看它的託管堆不就好啦。。。

從圖中可以看到,入口函式就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下來就可以在原始碼中全文檢索下。

<1> mysqld_main 入口函式 => sql/main.cc


extern int mysqld_main(int argc, char **argv);

int main(int argc, char **argv)
{
  return mysqld_main(argc, argv);
}

這裡大家可以用visualstudio開啟C++原始碼,使用檢視定義功能,非常好用。

<2> 建立監聽


int mysqld_main(int argc, char **argv)
{
    //建立服務監聽執行緒
    handle_connections_sockets();
}

void handle_connections_sockets()
{
     //監聽連線
     new_sock= mysql_socket_accept(key_socket_client_connection, sock,
                                    (struct sockaddr *)(&cAddr), &length);

    if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))
      thd->security_ctx->set_host((char*) my_localhost);

    //建立連線
    create_new_thread(thd);
}

//建立新執行緒處理處理使用者連線
static void create_new_thread(THD *thd){
   
   thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
   
   //執行緒進了執行緒排程器
   MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));   
}

至此mysql就開啟了一個執行緒對 3306 埠進行監控,等待客戶端請求觸發 add_connection 回撥。

2. 理解mysql是如何處理sql請求

這裡我以Insert操作為例稍微解剖下處理流程:

當使用者有請求sql過來之後,就會觸發 thread_scheduler的回撥函式add_connection


static scheduler_functions one_thread_per_connection_scheduler_functions=
{
  0,                                     // max_threads
  NULL,                                  // init
  init_new_connection_handler_thread,    // init_new_connection_thread
  create_thread_to_handle_connection,    // add_connection
  NULL,                                  // thd_wait_begin
  NULL,                                  // thd_wait_end
  NULL,                                  // post_kill_notification
  one_thread_per_connection_end,         // end_thread
  NULL,                                  // end
};

scheduler_functions 中可以看到,add_connection 對應了 create_thread_to_handle_connection,也就是請求來了會觸發這個函式,從名字也可以看出,用一個執行緒處理一個使用者連線。

<1> 客戶端請求被 create_thread_to_handle_connection 接管及呼叫棧追蹤


void create_thread_to_handle_connection(THD *thd)
{
     if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,
                                     handle_one_connection,(void*) thd))){}
}
//觸發回撥函式  handle_one_connection
pthread_handler_t handle_one_connection(void *arg)
{
     do_handle_one_connection(thd);
}
//繼續處理
void do_handle_one_connection(THD *thd_arg){
    while (thd_is_connection_alive(thd))
    {
      mysql_audit_release(thd);
      if (do_command(thd))  break;  //這裡的 do_command 繼續處理
    }
}
//繼續分發
bool do_command(THD *thd)
{
    return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
}
bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)
{
      switch (command) {
         case COM_INIT_DB: ....  break;
         ...
         case COM_QUERY:   //查詢語句:  insert xxxx
             mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);  //sql解析
           break;
      }
}
//sql解析模組
void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)
{
      error= mysql_execute_command(thd);
}

<2> 到這裡它的Parse,Optimizer,Cache都追完了,接下來看sql的CURD型別,繼續追。。。


//繼續執行
int mysql_execute_command(THD *thd)
{
  switch (lex->sql_command) 
  {
      case SQLCOM_SELECT:  res= execute_sqlcom_select(thd, all_tables);  break;

      //這個 insert 就是我要追的
      case SQLCOM_INSERT:   res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
		                                      lex->update_list, lex->value_list,
                                              lex->duplicates, lex->ignore);
  }
}
//insert插入操作處理
bool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,
                  List<Item> &update_fields, List<Item> &update_values, 
                  enum_duplicates duplic, bool ignore)
{
      while ((values= its++))
      {
           error= write_record(thd, table, &info, &update);
      }
}
//寫入記錄
int write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
{
    if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)
    {
         // ha_write_row  重點是這個函式
         while ((error=table->file->ha_write_row(table->record[0])))
         {
             ....
         }
    }
}

可以看到,呼叫鏈還是挺深的,追到 ha_write_row 方法基本上算是追到頭了,再往下的話就是 MySql ServerStorage Engine提供的介面實現了,不信的話繼續看唄。。。

<3> 繼續挖 ha_write_row


int handler::ha_write_row(uchar *buf)
{
    MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })
}

//這是一個虛方法
virtual int write_row(uchar *buf __attribute__((unused)))
{
    return HA_ERR_WRONG_COMMAND;
}

看到沒有,write_row是個虛方法,也就是給底層方法實現的,在這裡就是給各大Storage Engines的哈。???

3. 呼叫鏈圖

這麼多方法,看起來有點懵懵的吧,我來畫一張圖,幫助大家理解下這個呼叫堆疊。

三:總結

大家一定要熟讀架構圖,有了架構圖從原始碼中找資訊就方便多了,總之學習mysql成就感還是滿滿的?。


如您有更多問題與我互動,掃描下方進來吧~


圖片名稱

相關文章