mysql源代码分析Word文档下载推荐.docx
《mysql源代码分析Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《mysql源代码分析Word文档下载推荐.docx(29页珍藏版)》请在冰豆网上搜索。
大家看看文件名就大概知道是什么情况了。
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语句,生成语法树
寻找执行计划
根据解析出来的语法树,找到可能的执行计划。
对于一条SQL语句,很可能会有多种执行方案,特别是在SQL语句比较复杂的时候。
这里需要对于各种可能的方案进行代价评估,最快的找到最有的执行方案。
优化执行计划
优化执行计划。
这是SQL执行中最复杂的部分之一,据说全都是由数学博士们写出来的,而且比较难懂。
我目前还处于不懂的状态。
执行
没啥可说的,只剩执行及返回结果了
系统启动
所有的程序都从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,&
//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=&
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;
add_connection=create_thread_to_handle_connection;
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(&
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=&
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){
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){
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语句
Mysql源代码分析系列(4):
主要调用流程(续)
在上一篇文章中我们讲到了的mysql_execute_command,这个函数根据解析出来的SQL命令分别调用不同的函数做进一步处理。
我们这里先看"
INSERT"
命令的处理流程。
其对应的处理函数是mysql_insert,在sql/sql_insert.cc中,还是很长,大概300多行。
boolmysql_insert(THD*thd,
TABLE_LIST*table_list,
//该命令要用到的表
List<
Item>
fields,
//使用的域
List_item>
values_list,
update_fields,
update_values,
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==