阿亮 2018-09-30
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);
 }
 }
1
2
3
4
5
6
7
8
9
10从这里也可以看出,在短链接压力测试下,这会成为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
1
2
3
4
5
6
7
8
9在支持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;
 }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16MySQL支持很多种链接方式,包括企业版的连接池,都在这里体现。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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22mysql_thread_create函数会创建一个新的线程
然后开始关注线程函数
pfs_spawn_thread是初始化线程的入口函数,下面来看他是如何生成THD对象的
pfs_spawn_thread会调用handle_connection,主要负责如下几部分的工作
初始化thd
THD *thd= init_new_thd(channel_info); 1
写到这里,忽然想到了一个问题,全局变量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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26加入到线程队列中
thd_manager->add_thd(thd); 1
权限验证,从这里也可以看出,即时用户密码错误,MySQL也会分配线程,这是否占用线程资源呢?
thd_prepare_connection(thd) 1
如果以上都没有问题,则开始在一个循环中接收处理客户端命令了
while (thd_connection_alive(thd))
 {
 if (do_command(thd))
 break;
 }
1
2
3
4
5THD销毁
如果链接超时,或者客户端主动close掉链接,会销毁掉thd对象,大概有如下几步。
在end_connection操作中,会释放某个用户的链接,这在限制用户链接的功能中有用到。
真正的关闭链接,以及关闭前的处理操作
释放线程资源
从线程队列中踢除
再往下就是线程重用了
是否缓存此线程和MySQL中的全局变量thread_cache_size相关,如果当前缓存的线程数小于此值,则此线程会进入缓存状态,等待新的客户端到来,然后唤醒,否则的话,就退出线程了。
