mysql表的源代码怎么看_Mysql源代码分析系列(3):主要
调⽤流程--转载
本⽂主要介绍Mysql主要的调 ⽤流程,将从代码的⾓度来看⼀个从⽤户发出的"select * from test" SQL命令在服务器内部是如何被执⾏的。从我个⼈的经验来看,阅读理解⼤规模项⽬的代码最重要的两个⽅⾯,⼀是了解主要的数据结构,⼆是了解数据流,在这 ⾥主要是调⽤流程。把这两个主线把握住以后,⼤部分代码都是⽐较容易阅读的,Mysql的源代码属于⽐较好读的类型,因为函数的调⽤关系⽐较明确。难读的 代码⼀般都充斥着⼤量的回调、异步调⽤,很可能你极难到某个函数在哪⾥或什么时候被调⽤了。当然,算法的实现代码也很难读。幸好Mysql不是那种难读 的类型,所以我们也不要害怕,⼤步向前吧!
主要执⾏过程
从架构上来看,Mysql服务器对于⼀条SQL语句的执⾏过程可以分成如下⼏部分:
接受命令 包括⽤户验证,资源申请等
|
V
命令解析 解析SQL语句,⽣成语法树
|
V
寻执⾏计划 根据解析出来的语法树,到可能的执⾏计划。对于⼀条SQL语句,很可能会有多种执⾏⽅案,特别是在SQL语句⽐较复杂的时候。这⾥需要对于各种可能的⽅案进⾏代价评估,最快的到最有的执⾏⽅案。
|
V
优化执⾏计划 优化执⾏计划。这是SQL执⾏中最复杂的部分之⼀,据说全都是由数学博⼠们写出来的,⽽且⽐较难懂。我⽬前还处于不懂的状态。
access转mysql教程视频|
V
执⾏ 没啥可说的,只剩执⾏及返回结果了
系统启动
所有的程序都从main开始,mysqld也不例外,打开,稍加搜索,你就能看到熟悉的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);来完成,这⾥我们仔细看看这个函数调⽤发⽣了什么。该函数也在中,也有⼤概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是所有处理的⼊⼝,这⾥我们仔细看看它到底⼲了什么。幸运的是,它也在⾥⾯,我们不费吹灰之⼒就他了它:
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⼲了什么,现在打开⽂件:
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,它还是在中,哈哈:
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/中。
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/中。
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:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论