阿亮 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 16
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); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
mysql_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 5
THD销毁
如果链接超时,或者客户端主动close掉链接,会销毁掉thd对象,大概有如下几步。
在end_connection操作中,会释放某个用户的链接,这在限制用户链接的功能中有用到。
真正的关闭链接,以及关闭前的处理操作
释放线程资源
从线程队列中踢除
再往下就是线程重用了
是否缓存此线程和MySQL中的全局变量thread_cache_size相关,如果当前缓存的线程数小于此值,则此线程会进入缓存状态,等待新的客户端到来,然后唤醒,否则的话,就退出线程了。