当客户端输入 mysql -h x.x.x.x -u root -p 时,服务端在做什么(上)¶
Conventions
- 每一个代码块的顶部都有它所属的文件(相对)路径,如果代码块属于某个函数(方法),那么顶部会有函数(方法)的声明。
- 代码块中的对象,如果很重要,会在行尾补充该对象的声明。
- 我使用的MySQL 版本是 8.0.41。你看到这篇时,可能有了更新的版本,比如 8.0.42,区别不大的。
等待客户端连接¶
服务端在启动后监听 3306 端口,并且一直等待客户端连接。对应到源代码是什么样子的?
sql/main.cc | |
---|---|
首先,main 函数直接调用 mysqld_main 函数,够简单吧,那就去看看 mysqld_main 函数。
sql/mysqld.cc int mysqld_main(int argc, char **argv) | |
---|---|
- static Connection_acceptor
*mysqld_socket_acceptor
- Mysqld_socket_listener *m_listener
connection_event_loop() 函数没几行,从第 65 行看出,mysqld_socket_acceptor 的成员 m_listener 才是真正干活的———等待连接事件。第 66 行将(封装好的)连接对象 channel_info 交给 mgr 对象,mgr 对象调用 process_new_connection() 函数处理连接,就像它的名字 Connection_handler_manager 所说的,它是连接句柄的管理者。
苦力 m_listener 使用 POLL 系统调用,监听 POLLIN 事件。
接着,POLLIN 事件发生后,new 一个 channel_info 对象,类型 Channel_info_tcpip_socket,listen_for_connection_event 函数返回的就是这个对象。
- Connection_handler *m_connection_handler
处理连接¶
前面说到 mgr 调用 process_new_connection() 函数处理具体的连接对象 channel_info,上面就是该函数的全部代码,直接看第 263 行,它把连接对象 channel_info 交给了 m_connection_handler,m_connection_handler 是 mgr 的成员,对应类型 Connection_handler。
add_connection 函数只需要关注上面几行代码。
Connection_handler 有两个子类,一是 Per_thread_connection_handler,另一个是 One_thread_connection_handler。顾名思义,Per_thread_connection_handler 是一个连接一个线程,One_thread_connection_handler 是所有连接一个线程。我们看 Per_thread_connection_handler 就可以了,因为这是默认也是正常情况下用的,如果你一定要用 One_thread_connection_handler,那么可以修改 MySQL 配置 thread_handling=no-threads 即可。
从这里我们知道了,MySQL 服务端对于每一个客户端连接,都会创建一个线程来处理。按照套路,肯定得有线程池,第 407 行的函数名 check_idle_thread_and_enqueue_connection 就在提示我们,检查有没有空闲的线程,如果有那么就把连接对象 channel_info 扔进去。
我很自然地想到了线程池最大线程数的配置,于是查找官方文档,找到了 thread_cache_size。
扯远了,我们接着看上面这个函数,由于我们是服务端启动后第一次连接,所以 check_idle_thread_and_enqueue_connection 会返回 true 表示没有空闲线程,程序会走到第 415、416 行,mysql_thread_create 函数创建线程(内部使用 pthread API),并且将 handle_connection 函数指示符作为参数,这样线程就会执行 handle_connection 函数,而 (void *)channel_info 最终会作为 handle_connection 函数的入参。
sql/conn_handler/connection_handler_per_thread.cc static void *handle_connection(void *arg) | |
---|---|
线程执行的 handle_connection 函数如上所示,我们只需要关注这么一小块就行了。第 299 行 thd_prepare_connection 会验证用户名和密码,如果正确,那么返回 false,否则返回 true。
假设用户名密码正确,即 thd_prepare_connection 返回 false,程序会进入 else 分支。else 分支中是一个 while 循环,while 判断当连接存活时,调用 do_command 函数;连接不存活时,跳出循环,调用 end_connection 函数。
do_command 函数会阻塞等待客户端的下一条命令。
Tip
MySQL 源代码中很多函数成功时返回 false,失败时返回 true,要看注释。
小结¶
- 类
Connection_acceptor<Mysqld_socket_listener>
负责监听 3306 端口,与客户端建立连接。 - 类
Mysqld_socket_listener
是实际与客户端建立网络(TCP/IP)连接的牛马。 - 类
Channel_info
封装连接信息,子类Channel_info_tcpip_socket
表示 TCP/IP 连接。 - 类
Connection_handler
负责处理连接,子类Per_thread_connection_handler
对于每一个客户端连接,都会新建一个线程,处理逻辑在handle_connection
方法中。