前言
上周出现了几次连接超时、连接池满还有dbc连接事务模板失败的问题。所以有必要深入了解下MySQL的连接过程。
正好,上周研究了怎么用Clion调试MySQL源码,接下来通过调试来研究一下吧。
服务端
启动
sql/main.cc
1 | extern int mysqld_main(int argc, char **argv); |
- main:入口文件,仅调用了mysqld_main函数
sql/mysqld.cc
1 | int mysqld_main(int argc, char **argv) |
- mysql_main:MySQL服务端启动逻辑的主要处理函数
- my_init:系统库和线程初始化
- load_defaults:加载my.cnf各参数
- connection_event_loop:循环监听套接字。
sql/conn_handler/connection_acceptor.h
1 | /** |
- connection_event_loop:通过socket_connection.cc::listen_for_connection_event循环监听,直到有新的连接,开始connection_handler_manager.cc::process_new_connection新连接的处理过程。
新连接
服务端一直处于监听状态,当有新连接请求时,调用process_new_connection处理新连接。
sql/conn_handler/connection_handler_manager.cc
1 | void Connection_handler_manager::process_new_connection( |
- connection_events_loop_aborted:先判断是否已取消监听
- check_and_incr_conn_count:再判断(会加锁)是否现有连接数是否大于连接最大值(连接池满),未满,则将线程数加一,满了则拒绝连接。(注意,这里的判断逻辑使MySQL的实际最大连接数是max_connections + 1)
- add_connection:调用add_connection添加连接
sql/conn_handler/connection_handler_pre_thread.cc
1 | bool Per_thread_connection_handler::add_connection(Channel_info *channel_info) { |
- 调用check_idle_thread_and_enqueue_connection查看是否有空闲的线程,有则将本次连接信息加入等待队列,并给空闲线程发送唤醒信号;否则新建线程处理本次连接
- 在新线程中,调用handle_connection函数开始进行逻辑处理。
1 | static void *handle_connection(void *arg) { |
- 会对连接进行thd_prepare_connection预处理操作,没问题后继续下面的逻辑。
- 当连接未被关闭,就会一直do_command处理请求。
- 当连接关闭,则走下面关闭逻辑
执行
sql/sql_parse.cc
1 | bool do_command(THD *thd) { |
- 主要的处理逻辑为dispatch_command,根据不同的command类型进行分发。
1 | /** |
- 主要看COM_QUERY这个逻辑,我们要用到的DDL、DML都会走这个流程,这个流程中主要是调用mysql_parse方法
1 | /** |
- 主要是SQL语法解析和执行
- mysql_reset_thd_for_next_command是对下一次执行做准备,重置线程各变量
- mysql_rewrite_query看着像是SQL优化?待定 还没追进去,记个TODO
- 词法解析前不应该有缓存吗?没有找到缓存的逻辑,记个TODO(后续:原来MySQL8.0取消了query cache,详见:https://mysqlserverteam.com/mysql-8-0-retiring-support-for-the-query-cache/)
关闭连接
sql/conn_handler/connection_handler_pre_thread.cc
1 | Channel_info *Per_thread_connection_handler::block_until_new_connection() { |
- 如果阻塞的线程数小于最大阻塞线程数,则此线程不回收,而是进入阻塞状态(等待),等待新连接来的时候重复使用。
- 否则关闭线程。