1、Mysql源代码分析系列(2): 源代码结构 Mysql源代码主要包括客户端程序代码,服务器端代码,测试工具和一些库构成,下面我们对比较重要的目录做些介绍。 BUILD 这个目录在本系列的上篇文章中我们仔细看过,内含各种平台的编译脚本,这里就不仔细说了。 client 这个目录下有如下比较让人眼熟的文件: mysql.cc, mysqlcheck.c, mysqladmin.cc, mysqlshow.c,等等,如果你编译一下就会发现那些眼熟的程序也出现了,比如mysql。明白了吧,这个目录就是那些客户端程序所在的目录。这个目录的内容也比较少,而且也不是我们阅读的重点。 Docs 这个目录包含
2、了文档。 storage 这个目录包含了所谓的Mysql存储引擎 (storage engine)。存储引擎是数据库系统的核心,封装了数据库文件的操作,是数据库系统是否强大最重要的因素。Mysql实现了一个抽象接口层,叫做handler(sql/handler.h),其中定义了接口函数,比如:ha_open, ha_index_end, ha_create等等,存储引擎需要实现这些接口才能被系统使用。这个接口定义超级复杂,有900多行 :-(,不过我们暂时知道它是干什么的就好了,没必要深究每行代码。对于具体每种引擎的特点,我推荐大家去看mysql的在线文档: http:/ 应该能看到如下的目录
3、: * innobase, innodb的目录,当前最流行的存储引擎 * myisam, 最早的Mysql存储引擎,一直到innodb出现以前,使用最广的引擎。 * heap, 基于内存的存储引擎 * federated, 一个比较新的存储引擎 * example, csv,这几个大家可以作为自己写存储引擎时的参考实现,比较容易读懂 mysys 包含了对于系统调用的封装,用以方便实现跨平台。大家看看文件名就大概知道是什么情况了。 sql 这个目录是另外一个大块头,你应该会看到mysqld.cc,没错,这里就是数据库主程序mysqld所在的地方。大部分的系统流程都发生在这里。你还能看到sql_i
4、nsert.cc, sql_update.cc, sql_select.cc,等等,分别实现了对应的SQL命令。后面我们还要经常提到这个目录下的文件。 大概有如下及部分: SQL解析器代码: sql_lex.cc, sql_yacc.yy, sql_yacc.cc, sql_parse.cc等,实现了对SQL语句的解析操作。 handler代码: handle.cc, handler.h,定义了存储引擎的接口。 item代码:item_func.cc, item_create.cc,定义了SQL解析后的各个部分。 SQL语句执行代码: sql_update.cc, sql_insert.cc
5、sql_select.cc, sql_show.cc, sql_load.cc,执行SQL对应的语句。当你要看SELECT .的执行的时候,直接到sql_select.cc去看就OK了。 辅助代码: net_serv.cc实现网络操作 还有其他很多代码。 vio 封装了virtual IO接口,主要是封装了各种协议的网络操作。 plugin 插件的目录,目前有一个全文搜索插件(只能用在myisam存储引擎)。 libmysqld Mysql连接库源代码。 开源函数库目录 和所有的开源项目一样,Mysql也使用了一些开源的库,在其代码库中我们能看到dbug、pstack、strings、 zli
6、b等。 多说无益,主要是对于mysql的代码目录有个概念,要找的时候也有个方向。万一要找某个东西找不到了就只能grep了. Mysql源代码分析系列(3): 主要调用流程 引言 本文主要介绍Mysql主要的调用流程,将从代码的角度来看一个从用户发出的select * from test SQL命令在服务器内部是如何被执行的。从我个人的经验来看,阅读理解大规模项目的代码最重要的两个方面,一是了解主要的数据结构,二是了解数据流,在这里主要是调用流程。把这两个主线把握住以后,大部分代码都是比较容易阅读的,Mysql的源代码属于比较好读的类型,因为函数的调用关系比较明确。难读的代码一般都充斥着大量的回
7、调、异步调用,很可能你极难找到某个函数在哪里或什么时候被调用了。当然,算法的实现代码也很难读。幸好Mysql不是那种难读的类型,所以我们也不要害怕,大步向前吧! 主要执行过程 从架构上来看,Mysql服务器对于一条SQL语句的执行过程可以分成如下几部分: 接受命令 包括用户验证,资源申请等 | V 命令解析 解析SQL语句,生成语法树 | V 寻找执行计划 根据解析出来的语法树,找到可能的执行计划。对于一条SQL语句,很可能会有多种执行方案,特别是在SQL语句比较复杂的时候。这里需要对于各种可能的方案进行代价评估,最快的找到最有的执行方案。 | V 优化执行计划 优化执行计划。这是SQL执行中
8、最复杂的部分之一,据说全都是由数学博士们写出来的,而且比较难懂。我目前还处于不懂的状态。 | 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); / 解析配置文件和命令行参数,将配置文件中的内容转
9、行成命令行参数 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
10、(0); / 服务器初始化 init_status_vars(); / 状态变量初始化 create_shutdown_thread(); / 创建关闭线程 create_maintenance_thread(); / 创建维护线程 sql_print_information(.); / 打印一些信息 handle_connections_sockets(0); / 主要的服务处理函数,循环等待并接受命令,进行查询,返回结果,也是我们要详细关注的函数 wait for exit; / 服务要退出 cleanup; exit(0); 可以仔细的看看这个简写的main函数,逻辑很清楚,就算没有我的
11、这些注释大部分人也能容易的理解整个系统的执行流程。其实完整的main函数有接近300行,但是中心思想已经被包含在这里简短的十几行代码中了。 通过看这些代码,读者会发现mysqld是通过多线程来处理任务的,这点和Apache服务器是不一样的。 等待命令 mysqld等待和处理命令主要在handle_connections_sockets(0);来完成,这里我们仔细看看这个函数调用发生了什么。该函数也在mysqld.cc中,也有大概300行,我们继续简写。 为了方便分析,这里我们假定配置服务器通过unix domain socket来监听接受命令,其他方式类同。 pthread_handler_t
12、 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_connecti
13、on,&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任务线程描述符,它封装了一个客户端连接
14、请求的所有信息 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的代码很好懂呢?呵呵,更坚定了要继续读下去的信心。 一条语句的执行 下面具体看看服务
15、器如何执行语句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);
16、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_han
17、dle_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);
18、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_thr
19、ead(); 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=
20、(char*) net-read_pos; command= (enum enum_server_command) (uchar) packet0; / 解析客户端穿过来的命令类型 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; th
21、d-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
22、 char * found_semicolon) lex_start(thd); if (query_cache_send_result_to_client(thd, (char*) inBuf, length) 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, le
23、x-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语句 brea
24、k; Mysql源代码分析系列(4): 主要调用流程(续) 在上一篇文章中我们讲到了的mysql_execute_command,这个函数根据解析出来的SQL命令分别调用不同的函数做进一步处理。我们这里先看INSERT命令的处理流程。其对应的处理函数是mysql_insert,在sql/sql_insert.cc中,还是很长,大概300多行。 bool mysql_insert(THD *thd, TABLE_LIST *table_list, / 该命令要用到的表 List &fields, / 使用的域 List &values_list, List &update_fields, Lis
25、t &update_values, enum_duplicates duplic, bool ignored) open_and_lock_tables(thd, table_list); mysql_prepare_insert(.); foreach value in values_list write_record(.); 其实里面还有很多处理trigger,错误,view之类的,我们暂时都忽略。 / 写数据记录 int write_record(THD *thd, TABLE *table,COPY_INFO *info) if (info-handle_duplicates = DU
26、P_REPLACE | info-handle_duplicates = DUP_UPDATE) table-file-ha_write_row(table-record0); table-file-ha_update_row(table-record1, table-record0); else table-file-ha_write_row(table-record0); 不用说,这里我们还是省略了好多东西,要注意的是这里调用的table-file-ha_write_row和table-file-ha_update_row。在sql/table.h可以看到table的定义,其中file被定
27、义成handler *file; 那handler是什么?对了,这就是我们前面提到的数据存储抽象层,所有的存储引擎都必须事先这里定义的接口,才能被mysql使用。在这里使用的具体的接口操作是ha_write_row和ha_update_row。这两个函数可以在sql/handler.cc中看到。比如ha_write_row: int handler:ha_write_row(uchar *buf) write_row(buf); / 调用具体的实现 binlog_log_row(table, 0, buf, log_func); / 写binlog 下面我们看看在myisam中是怎么实现的文件
28、操作,代码在storage/myisam/ha_myisam.cc中。先看write_row: 723 int ha_myisam:write_row(uchar *buf) 724 725 ha_statistic_increment(&SSV:ha_write_count); 726 727 /* If we have a timestamp column, update it to the current time */ 728 if (table-timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) / 如果有timestamp域,写
29、入当前时间。 729 table-timestamp_field-set_time(); 730 731 /* 732 If we have an auto_increment column and we are writing a changed row 733 or a new row, then update the auto_increment value in the record. 734 */ 735 if (table-next_number_field & buf = table-record0) / 更新auto_increment列 736 737 int error;
30、738 if (error= update_auto_increment() 739 return error; 740 741 return mi_write(file,buf); / 真正写文件 742 再看mi_write函数,很好找,就在storage/myisam/mi_write.c,我们就不再分析下去了,具体实现和myisam使用的文件格式相关,有兴趣的可以参考myisam的相关文档。 结语 写到这里,我们对于mysql执行sql语句的主要调用流程就有了大概的了解,但是真正的难点往往在细节中,我也刻意逃避了这些细节,但是还是应该列一下相关的内容: + Sql语句的解析和相关数据结
31、构 + Sql语句的描述数据结构 + 执行优化相关算法 + 数据存储殷勤抽象层的定义和交互 + 存储引擎的具体操作和文件格式 必须要提到的是,这些地方的代码都比较难懂,而且核心函数都很长,非常不便与理解,有需要的人可以选一些方面具体深入,但要面面俱到就需要很多时间了。 Mysql源代码分析(5): Plugin架构介绍 Mysql现在很多模块都是通过plugin的方式连接到Mysql核心中的,除了大家熟悉的存储引擎都是Plugin之外,Mysql还支持其他类型的plugin。本文将对相关内容做一些简单介绍。主要还是以架构性的介绍为主,具体细节会提到一点,但是肯定不会包括所有的细节。 主要数据结
32、构和定义 大部分的数据接口,宏和常量都定义在include/mysql/plugin.h中,我们来慢慢看。 先看plugin的类型: #define MYSQL_UDF_PLUGIN 0 /* User-defined function */ #define MYSQL_STORAGE_ENGINE_PLUGIN 1 /* Storage Engine */ #define MYSQL_FTPARSER_PLUGIN 2 /* Full-text parser plugin */ #define MYSQL_DAEMON_PLUGIN 3 /* The daemon/raw plugin ty
33、pe */ #define MYSQL_INFORMATION_SCHEMA_PLUGIN 4 /* The I_S plugin type */ 开发者开发的plugin必须指定上述类型之一。类型包括用户自定义函数,存储引擎,全文解析,原声plugin和information schema plugin。最常见的是前三个,daemon plugin一般用来在mysqld中启动一个线程,在某些时候干活儿。 一个plugin的描述数据接口是: struct st_mysql_plugin int type; /* the plugin type (a MYSQL_XXX_PLUGIN value
34、) */ void *info; /* pointer to type-specific plugin descriptor */ const char *name; /* plugin name */ const char *author; /* plugin author (for SHOW PLUGINS) */ const char *descr; /* general descriptive text (for SHOW PLUGINS ) */ int license; /* the plugin license (PLUGIN_LICENSE_XXX) */ int (*init
35、)(void *); /* the function to invoke when plugin is loaded */ int (*deinit)(void *);/* the function to invoke when plugin is unloaded */ unsigned int version; /* plugin version (for SHOW PLUGINS) */ struct st_mysql_show_var *status_vars; struct st_mysql_sys_var *system_vars; void * _reserved1; /* re
36、served for dependency checking */ ; 主要内容包括类型,名字,初始化/清理函数,状态变量和系统变量的定义等等。但是在使用的时候一般不是直接使用这个数据结构,而是使用大量的宏来辅助。 一个plugin的开始: #define mysql_declare_plugin(NAME) _MYSQL_DECLARE_PLUGIN(NAME, builtin_ # NAME # _plugin_interface_version, builtin_ # NAME # _sizeof_struct_st_plugin, builtin_ # NAME # _plugin)
37、plugin定义结束: #define mysql_declare_plugin_end ,0,0,0,0,0,0,0,0,0,0,0,0 _MYSQL_DECLARE_PLUGIN根据plugin是动态链接plugin还是静态链接plugin有不同的定义: #ifndef MYSQL_DYNAMIC_PLUGIN #define _MYSQL_DECLARE_PLUGIN(NAME, VERSION, PSIZE, DECLS) int VERSION= MYSQL_PLUGIN_INTERFACE_VERSION; int PSIZE= sizeof(struct st_mysql_plugin); struct st_mysql_plugin DECLS= #else #define _MYSQL_DECLARE_PLUGIN(NAME, VERSION, PSIZE, DECLS) int _mysql_plugin_interface_version_= MYSQL_PLUGIN_INTERFACE_VERSION; int _mysql_sizeof_struct_st_plugin_= si