Mysql原始碼分析系列(3): 主要調用流程–轉載

來源:互聯網
上載者:User
文章目錄
  • 主要執行過程
  • 系統啟動
  • 等待命令
  • 一條語句的執行
引言

本文主要介紹Mysql主要的調 用流程,將從代碼的角度來看一個從使用者發出的"select * from test" SQL命令在伺服器內部是如何被執行的。從我個人的經驗來看,閱讀理解大規模項目的代碼最重要的兩個方面,一是瞭解主要的資料結構,二是瞭解資料流,在這 裡主要是調用流程。把這兩個主線把握住以後,大部分代碼都是比較容易閱讀的,Mysql的原始碼屬於比較好讀的類型,因為函數的調用關係比較明確。難讀的 代碼一般都充斥著大量的回調、非同步呼叫,很可能你極難找到某個函數在哪裡或什麼時候被調用了。當然,演算法的實現代碼也很難讀。幸好Mysql不是那種難讀 的類型,所以我們也不要害怕,大步向前吧!

主要執行過程

從架構上來看,Mysql伺服器對於一條SQL語句的執行過程可以分成如下幾部分:

接受命令 包括使用者驗證,資源申請等

   |

   V

命令解析 解析SQL語句,產生文法樹

   |

   V

尋找執行計畫 根據解析出來的文法樹,找到可能的執行計畫。對於一條SQL語句,很可能會有多種執行方案,特別是在SQL語句比較複雜的時候。這裡需要對於各種可能的方案進行代價評估,最快的找到最有的執行方案。

   |

   V

最佳化執行計畫 最佳化執行計畫。這是SQL執行中最複雜的部分之一,據說全都是由數學博士們寫出來的,而且比較難懂。我目前還處於不懂的狀態。

   |

   V

   執行 沒啥可說的,只剩執行及返回結果了

系統啟動

所有的程式都從main開始,mysqld也不例外,開啟sql/mysqld.cc,稍加搜尋,你就能看到熟悉的main函數,我們可以將其進行如下簡寫:

int main(int argc, char* argv[]) {

  logger.init_base();

init_common_variables(MYSQL_CONFIG_NAME, argc, argv, load_default_groups)); // 解析設定檔和命令列參數,將設定檔中的內容轉行成命令列參數

    init_signals();

    user_info= check_user(mysqld_user);

    set_user(mysqld_user, user_info);

    init_server_components(); // 初始化伺服器模組

    network_init(); // 初始化網路模組,根據配置,開啟IP socket/unix socket/windows named pipe來進行監聽。

   start_signal_handler(); // 開始接收訊號

   acl_init(...); // 初始化ACL (Access Control List)

   servers_init(0); // 伺服器初始化

    init_status_vars(); // 狀態變數初始化

   create_shutdown_thread(); // 建立關閉線程

   create_maintenance_thread(); // 建立維護線程

    sql_print_information(...); // 列印一些資訊

    handle_connections_sockets(0); // 主要的服務處理函數,迴圈等待並接受命令,進行查詢,返回結果,也是我們要詳細關注的函數

   wait for exit; // 服務要退出

   cleanup;

   exit(0);

}

可以仔細的看看這個簡寫的main函數,邏輯很清楚,就算沒有我的這些注釋大部分人也能容易的理解整個系統的執行流程。其實完整的main函數有接近300行,但是中心思想已經被包含在這裡簡短的十幾行代碼中了。


通過看這些代碼,讀者會發現mysqld是通過多線程來處理任務的,這點和Apache伺服器是不一樣的。


等待命令

mysqld等待和處理命令主要在handle_connections_sockets(0);來完成,這裡我們仔細看看這個函數調用發生了什麼。該函數也在mysqld.cc中,也有大概300行,我們繼續簡寫。

為了方便分析,這裡我們假定設定管理員通過unix domain socket來監聽接受命令,其他方式類同。

pthread_handler_t handle_connections_sockets(void *arg __attribute__((unused)))

{

   FD_ZERO(&clientFDs);

    FD_SET(unix_sock,&clientFDs); // unix_socket在network_init中被開啟

   socket_flags=fcntl(unix_sock, F_GETFL, 0);   while (!abort_loop) { // abort_loop是全域變數,在某些情況下被置為1表示要退出。      readFDs=clientFDs; // 需要監聽的socket      select((int) max_used_connection,&readFDs,0,0,0); // select非同步監聽,當接收到時間以後返回。      sock = unix_sock;      flags= socket_flags;   fcntl(sock, F_SETFL, flags | O_NONBLOCK);
   new_sock = accept(sock, my_reinterpret_cast(struct sockaddr *) (&cAddr),   &length); // 接受請求      getsockname(new_sock,&dummy, &dummyLen);      thd= new THD; // 建立mysqld任務線程描述符,它封裝了一個用戶端串連請求的所有資訊   vio_tmp=vio_new(new_sock, VIO_TYPE_SOCKET, VIO_LOCALHOST); // 網路操作抽象層   my_net_init(&thd->net,vio_tmp)); // 初始化任務線程描述符的網路操作
   create_new_thread(thd); // 建立任務線程

   }

}


看到這裡,大家應該已經基本清楚mysqld如何啟動並進入監聽狀態,真正的命令處理就是在create_new_thread裡面,看名字也知道就是建立一個新線程來處理任務。


怎麼樣,是不是覺得mysql的代碼很好懂呢?呵呵,更堅定了要繼續讀下去的信心。

一條語句的執行

下面具體看看伺服器如何執行語句"insert"語句的。


上一節我們提到create_new_thread是所有處理的入口,這裡我們仔細看看它到底幹了什麼。幸運的是,它也在mysqld.cc裡面,我們不費吹灰之力就找他了它:

static void create_new_thread(THD *thd) {

   NET *net=&thd->net;

   if (connection_count >= max_connections + 1 || abort_loop) { // 看看當前串連數是不是超過了系統配置允許的最大值,如果是就中斷連線。

   close_connection(thd, ER_CON_COUNT_ERROR, 1);

   delete thd;

   }

   ++connection_count;

   thread_scheduler.add_connection(thd); // 將新串連加入到thread_scheduler的串連隊列中。

}


現在看來關鍵還是在thread_scheduler幹了什麼,現在開啟sql/scheduler.cc檔案:

void one_thread_per_connection_scheduler(scheduler_functions* func) {

   func->max_threads= max_connections;

   func->add_connection= create_thread_to_handle_connection;

   func->end_thread= one_thread_per_connection_end;

}


再看create_thread_to_handle_connection,它還是在mysqld.cc中,哈哈:

void create_thread_to_handle_connection(THD *thd) {

    if (cached_thread_count > wake_thread) {

      thread_cache.append(thd);

      pthread_cond_signal(&COND_thread_cache);

   } else {

      threads.append(thd);

      pthread_create(&thd->real_id,&connection_attrib,   handle_one_connection,   (void*) thd)));

   }

}


恩,看來先是看當前背景工作執行緒緩衝(thread_cache)中有否空餘的線程,有的話,讓他們來處理,否則建立一個新的線程,該線程執行handle_one_connection函數


很好,繼續往下看,到了sql/sql_connection.cc中。

pthread_handler_t handle_one_connection(void *arg) {

   thread_scheduler.init_new_connection_thread();

    setup_connection_thread_globals(thd);

   for (;;) {

      lex_start(thd);

      login_connection(thd); // 進行串連身分識別驗證

      prepare_new_connection_state(thd);

      do_command(thd); // 處理命令      end_connection(thd);

   }

}


do_command在sql/sql_parse.cc中。

bool do_command(THD *thd) {

   NET *net= &thd->net;

    packet_length= my_net_read(net);

    packet= (char*) net->read_pos;

    command= (enum enum_server_command) (uchar) packet[0]; // 解析用戶端穿過來的命令類型

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

}


再看dispatch_command:

bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length) {

   NET *net= &thd->net;

   thd->command=command;

   switch (command) {

   case COM_INIT_DB: ...;

   case COM_TABLE_DUMP: ...;

   case COM_CHANGE_USER: ...;

   ...

   case COM_QUERY:

   alloc_query(thd, packet, packet_length);

       mysql_parse(thd, thd->query, thd->query_length, &end_of_stmt);

   }

}

進行sql語句解析

void mysql_parse(THD *thd, const char *inBuf, uint length, const char ** found_semicolon) {

    lex_start(thd);

   if (query_cache_send_result_to_client(thd, (char*) inBuf, length) <= 0) { // 看query cache中有否命中,有就直接返回結果,否則進行尋找

      Parser_state parser_state(thd, inBuf, length);   

      parse_sql(thd, & parser_state, NULL); // 解析sql語句

      mysql_execute_command(thd); // 執行

   }

}


總算開始執行了,mysql_execute_command函數超長,接近3k行:-(,我們還是按需分析吧。還是覺得這種代碼不應該出現在這種高水平的開源軟體裡面,至少在linux kernel中很少看見這麼長的函數,而在mysql裡面確實是常常看到。

int mysql_execute_command(THD *thd) {

    LEX *lex= thd->lex; // 解析過後的sql語句的文法結構

   TABLE_LIST *all_tables = lex->query_tables; // 該語句要訪問的表的列表

   switch (lex->sql_command) {

   ...

   case SQLCOM_INSERT:

   insert_precheck(thd, all_tables);

   check_table_access(thd, lex->exchange ? SELECT_ACL | FILE_ACL :   SELECT_ACL,   all_tables, UINT_MAX, FALSE); // 檢查使用者對資料表的存取權限

   execute_sqlcom_select(thd, all_tables); // 執行select語句

   break;

   }

}

   mysql_insert(thd, all_tables, lex->field_list, lex->many_values, lex->update_list, lex->value_list, lex->duplicates, lex->ignore);   break;   ...    case SQLCOM_SELECT:

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.