原创水平有限,有误请指出。仅仅作为学习参考和学习笔记。
源码版本 5.7.22
只研究了kill connection的情况。
最近看了丁奇老师的mysql课程中 kill connection的部分,在平时的工作中,我们也经常用kill 命令进行杀掉某些会话,偶尔也会出现状态还是killed的情况,Oracle也遇到过。不由得感觉需要研究一下kill 会话的是如何实现的。刚好丁奇老师的这段提供了理论基础。
一、简单的过程梳理和列子
先要简单的梳理一下语句的执行的生命周期:
打个比方我们以如下的执行计划为列子:
mysql> desc select * from t1 where name='gaopeng';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 14 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (1.67 sec)
某个客户端通过mysql客户端启动一个进程,通过socket IP:PORT的方式唯一确认一个mysqld服务器进程。
服务器进程mysqld准备好一个线程和这个mysql客户端进行网络通信。
mysql客户端发送命令通过mysql net协议到达mysqld服务器端。
mysqld服务端线程解包,获取mysql客户端发送过来的命令。
mysqld服务端线程通过权限认证,语法语义解析,然后经过物理逻辑优化生成一个执行计划。
loop:
mysqld服务端线程通过这个执行计划执行语句,首先innodb层会扫描出第一条数据,返回给mysql层进行过滤也就是是否符合条件name='gaopeng';。
如果符合则返回给 mysql客户端,如果不符合则继续loop。
直到loop 结束整个数据返回完成。
这里涉及到了一个mysql客户端进程和一个mysqld服务端线程,他们通过socket进行通信。如果我们要要kill某个会话我们显然一般是新开起来一个mysql客户端进程连接到mysqld服务端显然这个时候又需要开启一个服务端线程与其对接来响应你的kill命令那么这个时候图如下:
image.png
如图我们需要研究的就是线程2到底如何作用于线程1,实际上线程之间共享内存很简单这是线程的特性决定的,在MySQL中就共享了这样一个变量THD::killed,不仅线程1可以访问并且线程2也可以访问 。实际上这种情况就是依赖在代码的某些位置做了THD::killed的检查而实现。先大概先描述一下这种情况kill 会话的过程
线程2将THD::killed 设置
线程1在innodb层做扫描行的时候每行扫描完成后都会去检查自己的线程是否设置为了KILL_CONNECTION
如果设置为KILL_CONNECTION,那么做相应的终止过程
二、kill的不同情况
上面已经描述了一个select语句的kill的流程,但是并非都是这种情况,我稍微总结了一下可能的情况:
正在执行命令,如上的select的情况(非Innodb行锁等待情况)。
正在执行命令,如DML等待(Innodb行锁等待情况),需要Innodb层唤醒,代码则继续。
正在执行命令,MySQL层进行等待比如sleep命令,需要MySQL层唤醒,代码则继续。
空闲状态正在等待命令的到来。
注意上面的情况都是待杀线程处于的情况,而发起命令的线程只有一种方式,就是调用kill_one_thread函数。下面我将详细描述一下。对于唤醒操作参考附录的内容,我这里就默认大家都知道了。
三、发起kill命令的线程
下面是栈帧:
#0 THD::awake (this=0x7ffe7800e870, state_to_set=THD::KILL_CONNECTION) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:2206
#1 0x00000000015d5430 in kill_one_thread (thd=0x7ffe7c000b70, id=18, only_kill_query=false) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:6859
#2 0x00000000015d5548 in sql_kill (thd=0x7ffe7c000b70, id=18, only_kill_query=false) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:6887
kill_one_thread
这是一个主要的函数,他会根据待杀死的my_thread_id也就是我们kill后面跟的值,获取这个会话的THD结构体然后调用THD::awake函数如下:
tmp= Global_THD_manager::get_instance()->find_thd(&find_thd_with_id);//获得待杀死的会话的THD结构体
tmp->awake(only_kill_query ? THD::KILL_QUERY : THD::KILL_CONNECTION);//调用THD::awake命令我们这里是 THD::KILL_CONNECTION
THD::awake
这是一个主要的函数,这个函数会做将待杀死的会话的THD::killed标记为THD::KILL_CONNECTION,然后关闭socket连接,也就是这里客户端进程会收到一个类似如下的错误:
ERROR 2013 (HY000): Lost connection to MySQL server during query
然后还会做唤醒操作,关于为什么要做唤醒操作我们后面再说如下:
killed= state_to_set; \\这里设置THD::killed 状态为 KILL_CONNECTION
vio_cancel(active_vio, SHUT_RDWR); \\关闭socket连接,关闭socket连接后则客户端连接关闭
/* Interrupt target waiting inside a storage engine. */
if (state_to_set != THD::NOT_KILLED)
ha_kill_connection(this); \\lock_trx_handle_wait
mysql_mutex_lock(current_mutex);
mysql_cond_broadcast(current_cond); \\做