mysql源代码分析.docx
- 文档编号:3646766
- 上传时间:2022-11-24
- 格式:DOCX
- 页数:29
- 大小:33.13KB
mysql源代码分析.docx
《mysql源代码分析.docx》由会员分享,可在线阅读,更多相关《mysql源代码分析.docx(29页珍藏版)》请在冰豆网上搜索。
mysql源代码分析
Mysql源代码分析系列
(2):
源代码结构
Mysql源代码主要包括客户端程序代码,服务器端代码,测试工具和一些库构成,下面我们对比较重要的目录做些介绍。
BUILD
这个目录在本系列的上篇文章中我们仔细看过,内含各种平台的编译脚本,这里就不仔细说了。
client
这个目录下有如下比较让人眼熟的文件:
mysql.cc,mysqlcheck.c,mysqladmin.cc,mysqlshow.c,等等,如果你编译一下就会发现那些眼熟的程序也出现了,比如mysql。
明白了吧,这个目录就是那些客户端程序所在的目录。
这个目录的内容也比较少,而且也不是我们阅读的重点。
Docs
这个目录包含了文档。
storage
这个目录包含了所谓的Mysql存储引擎(storageengine)。
存储引擎是数据库系统的核心,封装了数据库文件的操作,是数据库系统是否强大最重要的因素。
Mysql实现了一个抽象接口层,叫做handler(sql/handler.h),其中定义了接口函数,比如:
ha_open,ha_index_end,ha_create等等,存储引擎需要实现这些接口才能被系统使用。
这个接口定义超级复杂,有900多行:
-(,不过我们暂时知道它是干什么的就好了,没必要深究每行代码。
对于具体每种引擎的特点,我推荐大家去看mysql的在线文档:
应该能看到如下的目录:
*innobase,innodb的目录,当前最流行的存储引擎
*myisam,最早的Mysql存储引擎,一直到innodb出现以前,使用最广的引擎。
*heap,基于内存的存储引擎
*federated,一个比较新的存储引擎
*example,csv,这几个大家可以作为自己写存储引擎时的参考实现,比较容易读懂
mysys
包含了对于系统调用的封装,用以方便实现跨平台。
大家看看文件名就大概知道是什么情况了。
sql
这个目录是另外一个大块头,你应该会看到mysqld.cc,没错,这里就是数据库主程序mysqld所在的地方。
大部分的系统流程都发生在这里。
你还能看到sql_insert.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.ccsql_select.cc,sql_show.cc,sql_load.cc,执行SQL对应的语句。
当你要看"SELECT..."的执行的时候,直接到sql_select.cc去看就OK了。
辅助代码:
net_serv.cc实现网络操作
还有其他很多代码。
vio
封装了virtualIO接口,主要是封装了各种协议的网络操作。
plugin
插件的目录,目前有一个全文搜索插件(只能用在myisam存储引擎)。
libmysqld
Mysql连接库源代码。
开源函数库目录
和所有的开源项目一样,Mysql也使用了一些开源的库,在其代码库中我们能看到dbug、pstack、strings、zlib等。
多说无益,主要是对于mysql的代码目录有个概念,要找的时候也有个方向。
万一要找某个东西找不到了就只能grep了...
Mysql源代码分析系列(3):
主要调用流程
引言
本文主要介绍Mysql主要的调用流程,将从代码的角度来看一个从用户发出的"select*fromtest"SQL命令在服务器内部是如何被执行的。
从我个人的经验来看,阅读理解大规模项目的代码最重要的两个方面,一是了解主要的数据结构,二是了解数据流,在这里主要是调用流程。
把这两个主线把握住以后,大部分代码都是比较容易阅读的,Mysql的源代码属于比较好读的类型,因为函数的调用关系比较明确。
难读的代码一般都充斥着大量的回调、异步调用,很可能你极难找到某个函数在哪里或什么时候被调用了。
当然,算法的实现代码也很难读。
幸好Mysql不是那种难读的类型,所以我们也不要害怕,大步向前吧!
主要执行过程
从架构上来看,Mysql服务器对于一条SQL语句的执行过程可以分成如下几部分:
接受命令 包括用户验证,资源申请等
|
V
命令解析 解析SQL语句,生成语法树
|
V
寻找执行计划 根据解析出来的语法树,找到可能的执行计划。
对于一条SQL语句,很可能会有多种执行方案,特别是在SQL语句比较复杂的时候。
这里需要对于各种可能的方案进行代价评估,最快的找到最有的执行方案。
|
V
优化执行计划 优化执行计划。
这是SQL执行中最复杂的部分之一,据说全都是由数学博士们写出来的,而且比较难懂。
我目前还处于不懂的状态。
|
V
执行 没啥可说的,只剩执行及返回结果了
系统启动
所有的程序都从main开始,mysqld也不例外,打开sql/mysqld.cc,稍加搜索,你就能看到熟悉的main函数,我们可以将其进行如下简写:
intmain(intargc,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(); //初始化网络模块,根据配置,打开IPsocket/unixsocket/windowsnamedpipe来进行监听。
start_signal_handler(); //开始接收信号
acl_init(...); //初始化ACL(AccessControlList)
servers_init(0); //服务器初始化
init_status_vars(); //状态变量初始化
create_shutdown_thread(); //创建关闭线程
create_maintenance_thread(); //创建维护线程
sql_print_information(...); //打印一些信息
handle_connections_sockets(0); //主要的服务处理函数,循环等待并接受命令,进行查询,返回结果,也是我们要详细关注的函数
waitforexit; //服务要退出
cleanup;
exit(0);
}
可以仔细的看看这个简写的main函数,逻辑很清楚,就算没有我的这些注释大部分人也能容易的理解整个系统的执行流程。
其实完整的main函数有接近300行,但是中心思想已经被包含在这里简短的十几行代码中了。
通过看这些代码,读者会发现mysqld是通过多线程来处理任务的,这点和Apache服务器是不一样的。
等待命令
mysqld等待和处理命令主要在handle_connections_sockets(0);来完成,这里我们仔细看看这个函数调用发生了什么。
该函数也在mysqld.cc中,也有大概300行,我们继续简写。
为了方便分析,这里我们假定配置服务器通过unixdomainsocket来监听接受命令,其他方式类同。
pthread_handler_thandle_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(structsockaddr*)(&cAddr), &length); //接受请求
getsockname(new_sock,&dummy,&dummyLen);
thd=newTHD; //创建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里面,我们不费吹灰之力就找他了它:
staticvoidcreate_new_thread(THD*thd){
NET*net=&thd->net;
if(connection_count>=max_connections+1||abort_loop){ //看看当前连接数是不是超过了系统配置允许的最大值,如果是就断开连接。
close_connection(thd,ER_CON_COUNT_ERROR,1);
deletethd;
}
++connection_count;
thread_scheduler.add_connection(thd); //将新连接加入到thread_scheduler的连接队列中。
}
现在看来关键还是在thread_scheduler干了什么,现在打开sql/scheduler.cc文件:
voidone_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中,哈哈:
voidcreate_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_thandle_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中。
booldo_command(THD*thd){
NET*net=&thd->net;
packet_length=my_net_read(net);
packet=(char*)net->read_pos;
command=(enumenum_server_command)(uchar)packet[0]; //解析客户端穿过来的命令类型
dispatch_command(command,thd,packet+1,(uint)(packet_length-1));
}
再看dispatch_command:
booldispatch_command(enumenum_server_commandcommand,THD*thd,char*packet,uintpacket_length){
NET*net=&thd->net;
thd->command=command;
switch(command){
caseCOM_INIT_DB:
...;
caseCOM_TABLE_DUMP:
...;
caseCOM_CHANGE_USER:
...;
...
caseCOM_QUERY:
alloc_query(thd,packet,packet_length);
mysql_parse(thd,thd->query,thd->query_length,&end_of_stmt);
}
}
进行sql语句解析
voidmysql_parse(THD*thd,constchar*inBuf,uintlength,constchar**found_semicolon){
lex_start(thd);
if(query_cache_send_result_to_client(thd,(char*)inBuf,length)<=0){ //看querycache中有否命中,有就直接返回结果,否则进行查找
Parser_stateparser_state(thd,inBuf,length);
parse_sql(thd,&parser_state,NULL); //解析sql语句
mysql_execute_command(thd); //执行
}
}
总算开始执行了,mysql_execute_command函数超长,接近3k行:
-(,我们还是按需分析吧。
还是觉得这种代码不应该出现在这种高水平的开源软件里面,至少在linuxkernel中很少看见这么长的函数,而在mysql里面确实是常常看到。
intmysql_execute_command(THD*thd){
LEX *lex=thd->lex; //解析过后的sql语句的语法结构
TABLE_LIST*all_tables=lex->query_tables; //该语句要访问的表的列表
switch(lex->sql_command){
...
caseSQLCOM_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;
...
caseSQLCOM_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;
}
}
Mysql源代码分析系列(4):
主要调用流程(续)
在上一篇文章中我们讲到了的mysql_execute_command,这个函数根据解析出来的SQL命令分别调用不同的函数做进一步处理。
我们这里先看"INSERT"命令的处理流程。
其对应的处理函数是mysql_insert,在sql/sql_insert.cc中,还是很长,大概300多行。
boolmysql_insert(THD*thd,
TABLE_LIST*table_list, //该命令要用到的表
List
List
List
List
enum_duplicatesduplic,
boolignored){
open_and_lock_tables(thd,table_list);
mysql_prepare_insert(...);
foreachvalueinvalues_list{
write_record(...);
}
}
其实里面还有很多处理trigger,错误,view之类的,我们暂时都忽略。
//写数据记录
intwrite_record(THD*thd,TABLE*table,COPY_INFO*info){
if(info->handle_duplicates==
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- mysql 源代码 分析