THD是MySQL server层最核心的类,没有之一,基本上所有的信息都是和THD 实例进行绑定的。对于每一个客户端链接进来后,系统会实例化一个THD对象,而后这个对象伴随这个链接的一生。
mysqld启动完毕后,main线程负责监听数据库对外服务端口,在循环中接受外部链接请求
代码如下
connection_event_loop
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);
}
}
从这里也可以看出,在短链接压力测试下,这会成为MySQL的一个瓶颈,本文中看到的是优化过后的5.7的代码,而之前的版本中,在初始化链接时,将更多的工作交给了main线程来完成,性能比较差。
listen_for_connection_event
Channel_info* Mysqld_socket_listener::listen_for_connection_event()
{
#ifdef HAVE_POLL
int retval= poll(&m_poll_info.m_fds[0], m_socket_map.size(), -1);
#else
m_select_info.m_read_fds= m_select_info.m_client_fds;
int retval= select((int) m_select_info.m_max_used_connection,
&m_select_info.m_read_fds, 0, 0, 0);
#endif
在支持poll的操作系统中,使用poll,否则使用select机制,poll相对于select机制的优化点网上有一大堆。
随后进入 mgr->process_new_connection(channel_info)
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;
}
}
MySQL支持很多种链接方式,包括企业版的连接池,都在这里体现。m_connection_handler->add_connection(channel_info)
而线上一般使用的是Per_thread_connection_handler
bool Per_thread_connection_handler::add_connection(Channel_info* channel_info)
{
int error= 0;
my_thread_handle id;
DBUG_ENTER("Per_thread_connection_handler::add_connection");
// Simulate thread creation for test case before we check thread cache
DBUG_EXECUTE_IF("fail_thread_create", error= 1; goto handle_error;);
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);
mysql_thread_create函数会创建一个新的线程
然后开始关注线程函数
pfs_spawn_thread是初始化线程的入口函数,下面来看他是如何生成THD对象的
pfs_spawn_thread会调用handle_connection,主要负责如下几部分的工作
初始化thd
THD *thd= init_new_thd(channel_info);
写到这里,忽然想到了一个问题,全局变量max_connections是在哪里生效的呢?这个问题另开一篇短文来介绍吧,先略过。
其他初始化操作,这里又涉及到线程重用的机制。
#ifdef HAVE_PSI_THREAD_INTERFACE
if (pthread_reused)
{
/*
Reusing existing pthread:
Create new instrumentation for the new THD job,
and attach it to this running pthread.
*/
PSI_thread *psi= PSI_THREAD_CALL(new_thread)
(key_thread_one_connection, thd, thd->thread_id());
PSI_THREAD_CALL(set_thread_os_id)(psi);
PSI_THREAD_CALL(set_thread)(psi);
}
#endif
#ifdef HAVE_PSI_THREAD_INTERFACE
/* Find the instrumented thread */
PSI_thread *psi= PSI_THREAD_CALL(get_thread)();
/* Save it within THD, so it can be inspected */
thd->set_psi(psi);
#endif /* HAVE_PSI_THREAD_INTERFACE */
mysql_thread_set_psi_id(thd->thread_id());
mysql_thread_set_psi_THD(thd);
mysql_socket_set_thread_owner(
thd->get_protocol_classic()->get_vio()->mysql_socket);
加入到线程队列中
thd_manager->add_thd(thd);
权限验证,从这里也可以看出,即时用户密码错误,MySQL也会分配线程,这是否占用线程资源呢?
thd_prepare_connection(thd)
如果以上都没有问题,则开始在一个循环中接收处理客户端命令了
while (thd_connection_alive(thd))
{
if (do_command(thd))
break;
}
如果链接超时,或者客户端主动close掉链接,会销毁掉thd对象,大概有如下几步。
在end_connection操作中,会释放某个用户的链接,这在限制用户链接的功能中有用到。
真正的关闭链接,以及关闭前的处理操作
释放线程资源
从线程队列中踢除
再往下就是线程重用了
是否缓存此线程和MySQL中的全局变量thread_cache_size相关,如果当前缓存的线程数小于此值,则此线程会进入缓存状态,等待新的客户端到来,然后唤醒,否则的话,就退出线程了。
欢迎加入MySQL内核交流qq群860945825