在mysqld_main函数中经过一系列的初始化后,mysql开始监听客户端的连接

mysqld_socket_acceptor->connection_event_loop();

查看mysqld_socket_acceptor:

Connection_acceptor<Mysqld_socket_listener> *mysqld_socket_acceptor= NULL;

  这是一个类模版Connection_acceptor通过Mysqld_socket_listener类进行的实例化,下面是Connection_acceptor的定义

template <typename Listener> class Connection_acceptor
{
Listener *m_listener; public:
Connection_acceptor(Listener *listener)
: m_listener(listener)
{ } ~Connection_acceptor()
{
delete m_listener;
} /**
Initialize a connection acceptor. @retval return true if initialization failed, else false.
*/
bool init_connection_acceptor()
{
return m_listener->setup_listener();
} /**
Connection acceptor loop to accept connections from clients.
*/
void connection_event_loop()
{
Connection_handler_manager *mgr= Connection_handler_manager::get_instance();
while (!abort_loop)
{
Channel_info *channel_info= m_listener->listen_for_connection_event();
if (channel_info != NULL)
mgr->process_new_connection(channel_info);
}
} /**
Close the listener.
*/
void close_listener()
{
m_listener->close_listener();
} };

  可以看出这个类是一个抽象接口封装了对于监听套接字的操作,connection_event_loop函数就是循环读取监听的套接字上的连接并且进行连接分配的函数。m_listener是一个指针,指向实际上获取连接的类Mysqld_socket_listener的实例,它的成员函数listen_for_connection_event()根据不同测操作系统的情况封装丢与套接字的监听操作,例如在linux下就使用poll函数进行操作。

  Connection_handler_manager是一个全局的单例模式,这个类用于管理获取的新连接如何进行处理,到底是使用每个连接一个线程,还是使用其他模式。这里暂时列出该类的几个与新建连接的关键成员和函数:

class Connection_handler_manager
{
static Connection_handler_manager* m_instance;
Connection_handler* m_connection_handler;
static mysql_mutex_t LOCK_connection_count;
static mysql_cond_t COND_connection_count; public:
static uint connection_count; // Protected by LOCK_connection_count
static ulong max_used_connections; // Protected by LOCK_connection_count
static ulong max_used_connections_time;// Protected by LOCK_connection_count //获取单例模式指向实例的指针
static Connection_handler_manager* get_instance()
{
DBUG_ASSERT(m_instance != NULL);
return m_instance;
} //处理新连接
void process_new_connection(Channel_info* channel_info);
bool check_and_incr_conn_count();
}

  m_connection_handler是具体的连接处理者,其类型Connection_handler是一个虚基类,各种连接方式继承这个类具体实现如何处理连接,process_new_connection函数为接收新连接后进行处理的函数:

Connection_handler_manager::process_new_connection(Channel_info* channel_info)
{
if (abort_loop || !check_and_incr_conn_count())
{
channel_info->send_error_and_close_channel(ER_CON_COUNT_ERROR, 0, true);
delete channel_info;
return;
} if (m_connection_handler->add_connection(channel_info))
{
inc_aborted_connects();
delete channel_info;
}
}

  这个函数先靠abort_loop判断是否停止监控,这是一个定义在mysqld.cc中的volatile全局变量,可能被其他线程改变。check_and_incr_conn_count函数用来增加连接计数,当连接数大于最大连接数时,新增连接失败。但是值得注意的是当连接数等于最大连接时依然允许再建立一个连接,这个连接是为root用户管理使用的,在连接后会进行验证。因此实际上的最大连接数是:最大连接数+1。如果循环未停止且连接数未满,则调用m_connection_handler的add_connection添加连接,比较奇怪的是这里返回false表示正常?。这里就是一个典型的多态,具体的处理要看继承Connection_handler的类。这里以每连接每线程为例子,其实现Per_thread_connection_handler类如下:

//sql/conn_handler/connection_handler_impl.h
class Per_thread_connection_handler : public Connection_handler
{
Per_thread_connection_handler(const Per_thread_connection_handler&);
Per_thread_connection_handler&
operator=(const Per_thread_connection_handler&); /**
Check if idle threads to handle connection in
thread cache. If so enqueue the new connection
to be picked by the idle thread in thread cache. @retval false if idle pthread was found, else true.
*/
bool check_idle_thread_and_enqueue_connection(Channel_info* channel_info); /**
List of pending channel info objects to be picked by idle
threads. Protected by LOCK_thread_cache.
*/
static std::list<Channel_info*> *waiting_channel_info_list; static mysql_mutex_t LOCK_thread_cache;
static mysql_cond_t COND_thread_cache;
static mysql_cond_t COND_flush_thread_cache; public:
// Status variables related to Per_thread_connection_handler
static ulong blocked_pthread_count; // Protected by LOCK_thread_cache.
static ulong slow_launch_threads;
// System variable
static ulong max_blocked_pthreads; static void init();
static void destroy(); /**
Wake blocked pthreads and wait until they have terminated.
*/
static void kill_blocked_pthreads(); /**
Block until a new connection arrives.
*/
static Channel_info* block_until_new_connection(); Per_thread_connection_handler() {}
virtual ~Per_thread_connection_handler() { } protected:
virtual bool add_connection(Channel_info* channel_info); virtual uint get_max_threads() const;
};

  这个类是用于在one thread per connection的模式下管理连接与线程,在这里可以详细探究下。首先,mysql的one thread per connection模式下,每个连接占用一个线程,mysql会缓存一部分线程以供重用,最大数量由该类中的max_blocked_pthreads控制(这个变量在mysqld.cc中的init_common_variables中按照用户设置进行初始化)

  线程缓存的实现实际上使用了类似阻塞队列的方式,可以将这个看成一个近似生产者消费者的模型,waiting_channel_info_list中为待消费的元素,这里就是保存连接信息的channel_info的指针。当一个线程处理完自己的任务时,调用block_until_new_connection函数:

Channel_info* Per_thread_connection_handler::block_until_new_connection()
{
Channel_info *new_conn= NULL;
mysql_mutex_lock(&LOCK_thread_cache);
if (blocked_pthread_count < max_blocked_pthreads &&
!kill_blocked_pthreads_flag)
{
/* Don't kill the pthread, just block it for reuse */
DBUG_PRINT("info", ("Blocking pthread for reuse")); /*
mysys_var is bound to the physical thread,
so make sure mysys_var->dbug is reset to a clean state
before picking another session in the thread cache.
*/
DBUG_POP();
DBUG_ASSERT( ! _db_is_pushed_()); // Block pthread
blocked_pthread_count++;
while (!abort_loop && !wake_pthread && !kill_blocked_pthreads_flag)
mysql_cond_wait(&COND_thread_cache, &LOCK_thread_cache);
blocked_pthread_count--; if (kill_blocked_pthreads_flag)
mysql_cond_signal(&COND_flush_thread_cache);
else if (!abort_loop && wake_pthread)
{
wake_pthread--;
DBUG_ASSERT(!waiting_channel_info_list->empty());
new_conn= waiting_channel_info_list->front();
waiting_channel_info_list->pop_front();
DBUG_PRINT("info", ("waiting_channel_info_list->pop %p", new_conn));
}
}
mysql_mutex_unlock(&LOCK_thread_cache);
return new_conn;
}

  从最外层的if语句可以看出,当被阻塞以缓存起来的线程数量未达到最大值,且kill_blocked_pthreads_flag标志未被设置时(当关闭时,该标志被设置,用来结束所有被阻塞的线程),线程将阻塞在条件变量COND_thread_cache上:

while (!abort_loop && !wake_pthread && !kill_blocked_pthreads_flag)
mysql_cond_wait(&COND_thread_cache, &LOCK_thread_cache);

  当有新连接的到来时,会检查是否有被缓存的线程,有的话就会使用COND_thread_cache唤醒阻塞在上面的线程,由代码可以看出若唤醒的原因是由于新连接的到来的话,则被唤醒的线程会去队列中取出一个待处理的连接并且返回这个连接,然后进行处理。

else if (!abort_loop && wake_pthread)
{
wake_pthread--;
DBUG_ASSERT(!waiting_channel_info_list->empty());
new_conn= waiting_channel_info_list->front();
waiting_channel_info_list->pop_front();
DBUG_PRINT("info", ("waiting_channel_info_list->pop %p", new_conn));
}

  接下来看如何加入新连接,从名字就可以看出,加入新连接的函数为 add_connection,上面已经提到过这个函数是继承自虚基类Connection_handler的一个虚函数,连接管理类Connection_handler_manager的process_new_connection函数调用 add_connection 来实际添加连接,下面来看看这个函数的主要代码

bool Per_thread_connection_handler::add_connection(Channel_info* channel_info)
{
int error= 0;
my_thread_handle id;
//省略调试信息
···
if (!check_idle_thread_and_enqueue_connection(channel_info))
DBUG_RETURN(false); /*
There are no idle threads avaliable to take up the new
connection. Create a new thread to handle the connection
*/
channel_info->set_prior_thr_create_utime();
error= mysql_thread_create(key_thread_one_connection, &id,
&connection_attrib,
handle_connection,
(void*) channel_info);
#ifndef DBUG_OFF
handle_error:
#endif // !DBUG_OFF if (error)
{
//错误处理
···
} Global_THD_manager::get_instance()->inc_thread_created();
DBUG_PRINT("info",("Thread created"));
DBUG_RETURN(false);
}

  精简代码后逻辑比较清晰,程序先调用check_idle_thread_and_enqueue_connection查看是否有可用的被缓存起来的线程,如果有就直接将channelinfo插入队列并唤醒阻塞的线程,如果没有则新建一个连接处理的线程。check_idle_thread_and_enqueue_connection代码如下:

bool Per_thread_connection_handler::check_idle_thread_and_enqueue_connection(Channel_info* channel_info)
{
bool res= true; mysql_mutex_lock(&LOCK_thread_cache);
if (Per_thread_connection_handler::blocked_pthread_count > wake_pthread)
{
DBUG_PRINT("info",("waiting_channel_info_list->push %p", channel_info));
waiting_channel_info_list->push_back(channel_info);
wake_pthread++;
mysql_cond_signal(&COND_thread_cache);
res= false;
}
mysql_mutex_unlock(&LOCK_thread_cache); return res;
}

  这个函数较为简单,当有空余线程的时候,就将待处理的连接加入队列并且向条件变量发送信号并唤醒一个线程进行处理。这里wake_pthread的含义可以认为是还需要多少个线程去取连接,wake_pthread自增后会去唤醒一个阻塞的线程,待取得一个连接后wake_pthread再自减,所以只有当blocked_pthread_count > wake_pthread的时候才会有空闲的线程,否则需要新建线程:

error= mysql_thread_create(key_thread_one_connection, &id,
&connection_attrib,
handle_connection,
(void*) channel_info);

  这里就是具体处理连接的入口,下面是精简之后的handle_connection函数:

extern "C" void *handle_connection(void *arg)
{
Global_THD_manager *thd_manager= Global_THD_manager::get_instance();
Connection_handler_manager *handler_manager=
Connection_handler_manager::get_instance();
Channel_info* channel_info= static_cast<Channel_info*>(arg);
bool pthread_reused MY_ATTRIBUTE((unused))= false; if (my_thread_init())
{
//错误处理 PS:mysql有些函数错误处理有点奇葩,中有的函数返回false表示正确,true表示错误
···
} for (;;)
{
THD *thd= init_new_thd(channel_info);
if (thd == NULL)
{
//错误处理
break; // We are out of resources, no sense in continuing.
}
···
thd_manager->add_thd(thd); if (thd_prepare_connection(thd))
handler_manager->inc_aborted_connects();
else
{//当连接存活时,循环处理命令
while (thd_connection_alive(thd))
{
if (do_command(thd))
break;
}
end_connection(thd);
}
//做关闭连接后的善后工作
close_connection(thd, 0, false, false);
thd->get_stmt_da()->reset_diagnostics_area();
thd->release_resources();
ERR_remove_state(0);
thd_manager->remove_thd(thd);
Connection_handler_manager::dec_connection_count();
delete thd; if (abort_loop) // Server is shutting down so end the pthread.
break;
//阻塞在这里等待新连接(当被缓存的线程数未达到最大值的时候)
channel_info=Per_thread_connection_handler::block_until_new_connection();
if (channel_info == NULL)
break;
pthread_reused= true;
}
//退出线程
my_thread_end();
my_thread_exit(0);
return NULL;
}

  这里首先获取了线程管理的单例类Global_THD_manager,和连接的管理类Connection_handler_manager。Global_THD_manager将各个线程的THD结构体串联在一个链表中统一管理,这里先不展开。在使用每连接每线程的模式时,一个THD结构对应一个线程,但并不总是这样,例如使用线程池的话就并非如此。为了不混淆,之后所称的线程其实指一个开始处理的连接,线程结构体为其对应的THD结构体。若要指代真正的线程,会使用真实线程或者物理线程的说法。

  随后进行了线程初始化,主要是初始化了一些线程局部变量,用于DEBUG。之后的for循环就是一个线程的主要逻辑所在

  循环中首先创建了这个线程的结构体THD,这个结构体保存了一个线程的上下文信息,非常重要,经常作为一些函数的参数。例如一个THD对应一个客户端连接时,THD结构体里就包含了连接的所有信息,权限,帐号,事务状态等等。随后这个THD被加入了Global_THD_manager管理的链表中。

  在对连接进行验证(例如权限验证)后,进入循环:

while (thd_connection_alive(thd))
{
if (do_command(thd))
break;
}
end_connection(thd);

  这里就是对一个连接的命令的处理,循环接收连接的指令并执行,直到连接被killed或者执行出现某些错误再退出。再退出循环后进行连接善后工作并如同上文所说的一样调用Per_thread_connection_handler::block_until_new_connection()尝试将该线程缓存起来供下一个连接使用。最后当线程退出时,进行一些清理工作。

  do_command函数就是接收执行客户端命令的函数,将网络协议以及错误处理等精简后,得到其主要逻辑的代码如下:

//sql/sql_parse.cc
bool do_command(THD *thd)
{
NET *net= NULL;
enum enum_server_command command;
COM_DATA com_data;
rc= thd->get_protocol()->get_command(&com_data, &command);
return_value= dispatch_command(thd, &com_data, command);
}

  可以看到,每一次循环从网络读取指令数据和类型,然后给dispatch_command进行具体的命令执行,当一个连接新创建或者没有任何请求的时候线程就会阻塞在这里。到此,就进入了mysql的命令分发流程。dispatch_command函数体非常的长,这里只保留部分主要逻辑。

//sql/sql_parse.cc
bool dispatch_command(THD *thd, const COM_DATA *com_data,
enum enum_server_command command)
{
bool error= 0;
Global_THD_manager *thd_manager= Global_THD_manager::get_instance();
thd->set_command(command);
thd->enable_slow_log= TRUE;
thd->lex->sql_command= SQLCOM_END; /* to avoid confusing VIEW detectors */
thd->set_time(); thd_manager->inc_thread_running();
switch (command) {
case COM_INIT_DB:
···
case COM_REGISTER_SLAVE:
···
···
case COM_QUERY:
{
···
if (alloc_query(thd, com_data->com_query.query,
com_data->com_query.length))
break;
···
mysql_parse(thd, &parser_state);
···
}
}

  这个函数的主要逻辑就是一个巨无霸switch case 语句,根据命令的类型来进行不同的处理。这里注意COM_QUERY,虽然他的名字有“QUERY”,看上去像处理查询的,实际上所有对于数据库的访问操作都是通过这里进入的,如常见的DDL、DML语句......将代码简化后的逻辑为:

//sql/sql_pasrse.cc
void mysql_parse(THD *thd, Parser_state *parser_state)
{
//为查询缓存作初始化
mysql_reset_thd_for_next_command(thd);
lex_start(thd); if (query_cache.send_result_to_client(thd, thd->query()) <= 0)
{
···
err= parse_sql(thd, parser_state, NULL);
···
error= mysql_execute_command(thd, true);
···
}
···
}

  这里只列出了主要的入口,其他的细节暂时不清楚....这个函数先判断查询的语句是否在查询缓存里面(只有完全一样的语句才能匹配,甚至包括空格和大小写),若在则直接发送给客户端,不然就对语句进行语法解析,然后真正的执行它。执行函数为mysql_execute_command

int
mysql_execute_command(THD *thd, bool first_level)
{
//保存解析后的语法树
LEX *const lex= thd->lex;
//需要访问哪些表
TABLE_LIST *all_tables;
all_tables= lex->query_tables;
···
switch (lex->sql_command)
{ case SQLCOM_SHOW_STATUS:
···
case SQLCOM_INSERT:
case SQLCOM_REPLACE_SELECT:
case SQLCOM_INSERT_SELECT:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
DBUG_ASSERT(lex->m_sql_cmd != NULL);
res= lex->m_sql_cmd->execute(thd);
break;
}
···
case SQLCOM_SHOW_PROCESSLIST:
···
}
}

  这里就是最终对于各个语句具体的操作的入口,这又是一个巨大的switch case语句,其中的每个case就是我们熟悉的指令,对于其中每个指令的执行,就不再深入了,到这里已经完成了对于连接处理的跟踪。

mysql的连接处理过程的更多相关文章

  1. MySQL建立连接的过程

    数据库连接的一些知识: import java.sql.Connection ; import java.sql.DriverManager ; import java.sql.Statement ; ...

  2. mysql中连接失败2003错误解决办法

    在使用mysql数据库,新建连接时,会报2003-Can't connect to server on 'localhost'(10038)错误,原因主要是MYSQL服务没有启动起来,但是进入:计算机 ...

  3. mysql中SQL执行过程详解与用于预处理语句的SQL语法

    mysql中SQL执行过程详解 客户端发送一条查询给服务器: 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果.否则进入下一阶段. 服务器段进行SQL解析.预处理,在优化器生成对应的 ...

  4. 创建本地数据库mySQL并连接JDBC

    转自: http://blog.csdn.net/wei_chong_chong/article/details/44830491 如何创建本地数据库MySQL并连接JDBC 转载 2015年04月0 ...

  5. mysql和连接相关的timeout

    MySQL和连接相关的timeout 今天同事问为什么查询mysql库时, 在数据量比较大时,会话总断.刚开始以为是mysql的和连接有关timeout的问题,结果是网络的不稳定的原因. 下面总结下和 ...

  6. python selenium 测试环境的搭建及python mysql的连接

    又来一篇傻瓜教程啦,防止在学习的小伙伴们走弯路. 1.python 环境搭建 python官网:https://www.python.org/downloads/  选择最新版本python下载(如果 ...

  7. Jedis 与 MySQL的连接线程安全问题

    Jedis的连接是非线程安全的 下面是set命令的执行过程,简单分为两个过程,客户端向服务端发送数据,服务端向客户端返回数据,从下面的代码来看:从建立连接到执行命令是没有进行任何并发同步的控制 pub ...

  8. Cacti监控mysql数据库服务器实现过程

    Cacti监控mysql数据库服务器实现过程 2014-05-29      0个评论    来源:Cacti监控mysql数据库服务器实现过程   收藏    我要投稿 1 先在cacti服务器端安 ...

  9. mysql远程连接问题 Lost connection to MySQL server at ‘reading initial communication packet', system error: 0

    在用Navicat for MySQL远程连接mysql的时候,出现了 Lost connection to MySQL server at ‘reading initial communicatio ...

随机推荐

  1. es6对象字面量增强

    相对于ES5,ES6的对象字面量得到了很大程度的增强.这些改进我们可以输入更少的代码同时语法更易于理解.那就一起来看看对象增强的功能.对象字面量简写(Object Literal Shorthand) ...

  2. Mysql必须知道的知识

    最近在准备面试,所以也整理了一些Mysql数据库常用的知识,供大家参考. 1.MySQL的复制原理以及流程 (1).复制基本原理流程 1. 主:binlog线程--记录下所有改变了数据库数据的语句,放 ...

  3. tomca配置文件自动还原问题的解决 server.xml content.xml 等

    当我们在处理中文乱码或是配置数据源时,我们要修改Tomcat下的server.xml和content.xml文件. 但是当我们修改完后重启Tomcat服务器时发现xml文件又被还原了,修改无效果. 为 ...

  4. Python中字符串颜色

    格式:\033[显示方式;前景色;背景色m 说明: 前景色 背景色 颜色 --------------------------------------- 30 40 黑色 31 41 红色 32 42 ...

  5. web框架之Flask

    Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后 ...

  6. C#多线程+委托+匿名方法+Lambda表达式

    线程 下面是百度写的: 定义英文:Thread每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.进程也可能是整个程序或者是部分程序的动态执行.线程是一组指令的集合,或者是程序的特殊段,它 ...

  7. Django项目实战之用户上传与访问

    1 将文件保存到服务器本地 upload.html <!DOCTYPE html> <html lang="en"> <head> <me ...

  8. JS小测验

    1.编写一个方法method(),判断一个数能否同时被3和5整除 <div class="one" onClick="method()"> func ...

  9. BST讲解

    BST 第一步,什么是BST,所谓BST就是满足一种特定性质的二叉树,这个性质一般情况是当前节点的权值比他的左子树的所有点的权值大,比他的右子树的所有点的权值小,满足这样性质的二叉树就称为BST,下面 ...

  10. c#代码输入图片

    Image bgimage = Image.FromFile(flieUrl + bgImg); if (bgimage != null) { Bitmap bmp1 = new Bitmap(bgi ...