设为首页 收藏本站
查看: 804|回复: 0

[经验分享] Mysql源代码分析系列(2): 主要调用流程

[复制链接]

尚未签到

发表于 2015-12-22 11:20:35 | 显示全部楼层 |阅读模式
引言  

本文主要介绍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)  
       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);

  
           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:   
  
          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;
   }
}

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-154713-1-1.html 上篇帖子: MySQL 性能优化之索引优化 下篇帖子: MySQL源码分析(1):源码结构及主要数据结构
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表