kill 指令有两种写法 " kill query + 线程 id "、" kill connection(可缺省) + 线程 id "。分别表示关闭指定线程正在执行的语句、断开指定线程链接的客户端(若是有正在执行的操做会先中止执行的操做再关闭链接)。但某些状况下使用 kill query 后使用 show processlist 查看 Command 列为 killed(表示 正在等待回收线程回收,还未回收),这是为何呢?mysql
在解答这个问题前,须要知道服务器端处理请求的线程是如何执行的,以及 kill 命令是如何做用的。sql
一、 一个语句执行过程当中有多处 " 埋点 ",在这些 " 埋点 " 的地方判断线程状态,若是发现线程状态是 THD:KILL_QUERY,才开始进入语句终止逻辑;数据库
二、若是处于等待状态,必须是一个能够被唤醒的等待,不然根本不会执行到“埋点”处;缓存
三、语句从开始进入终止逻辑,到终止逻辑彻底完成,是有一个过程的。服务器
kill query 主要进行了两步操做:网络
一、把线程的运行状态改为 THD::KILL_QUERY(将变量 killed 赋值为 THD::KILL_QUERY);session
二、给会话的执行线程发一个信号,退出阻塞状态,处理这个状态。并发
一、把 12 号线程状态设置为 KILL_CONNECTION;socket
二、关掉 12 号线程的网络链接。函数
一、通常正常执行的语句在执行 kill query 后都会先将状态从 killed 改为 KILL_QUERY,而后执行到 " 埋点 " 处被判断中断执行。
二、若是是处于阻塞的语句,那么须要去查看当前阻塞等待的状态是否能够被唤醒,若是能够被唤醒才有机会中断当前语句。
例子:因行锁阻塞。
由于等行锁时,使用的是 pthread_cond_timedwait 函数,因此这个等待状态能够被唤醒。能够被 kill query 直接唤醒继续执行直到 "埋点" 判断。
例子:因并发线程被使用完而形成的阻塞。
将参数 innodb_thread_concurrency(MySQL 的并发线程数)设为 2。而后执行下面的操做:
在 sessionD 执行 kill query C 后 sessionC 并无退出阻塞。
问题1:为何使用 kill query 没有中断阻塞?
答:由于这种阻塞从微观上来看并非阻塞,而是一种循环判断。每隔 10 毫秒判断一下是否能够进入 Innodb 执行,若是不行,就调用 nanosleep 函数进入 sleep 状态。也就是说,虽然线程的状态已经被设置成了 KILL_QUERY(THD::KILL_QUERY),可是在这个等待进入 InnoDB 的循环过程当中,并无执行到 "埋点",也就没有去判断线程的状态,所以根本不会进入终止逻辑阶段。因此也就不会中断。
问题2:若是此时使用 show processlist 来查看,会发现 Command 列为 killed,这是为何?
答:kill query 语句会将线程状态设为 KILL_QUERY ,这时会由于这个状态而被判断为正在执行中断逻辑,因此 Command 值为 killed。
问题3:为何使用 kill connection 能够中断阻塞?
答:由于 kill connection 会直接关闭线程的网络链接,强制关闭,因此这时候 session C 收到了断开链接的提示。
问题4:若是只是使用 kill query 何时才能中断阻塞?
答:只有等到会话被分配了线程后执行到 “ 埋点 ” 后判断而后执行中断逻辑才会退出。而被分配线程后并非就必定会中断,若是在执行到 "埋点" 以前让出线程,那么就会再次等待。MySQL 的线程是多路复用的。
一、其实除了上面使用 kill 命令来终止阻塞状态外,还能够直接在该会话中使用 “ ctrl+c ” 来停止阻塞,这又是什么原理呢?
答:首先要知道客户端操做服务端是客户端开启一个线程,让这个线程去处理,发送请求数据,经过网络传输到服务端,服务端再分配线程去处理。而 "ctrl +c " 是让客户端另开一个链接,并发送一个 kill query 的命令。因此虽然咱们看来是中断了阻塞,可是处理上一个链接的服务端线程并必定就会被中断。
二、为何在指定库名链接时会很慢?以下图:
答:这是因为 MySQL 默认开启了自动补全功能(输入表名时可使用 tab 自动补全)。其实现是在链接数据库多执行一些操做:
一、执行 show databases;
二、切到 db1 库,执行 show tables;
三、把这两个命令的结果用于构建一个本地的哈希表。(最耗时)
这个功能能够在命令中加上 -A 关闭。同时使用 -quick 也能够关闭。可是使用 -quick 可能会使客户端性能下降。这是为何?这就要说到数据在服务器端与客户端发送的流程了。
客户端首先与服务器端验证用户名和密码,经过后正式创建链接,而后客户端发送请求,服务器端从线程池中取一个线程来处理。处理的过程:
一、获取一行,写到 net_buffer 中。这块内存的大小是由参数 net_buffer_length 定义的,默认是 16k。
二、重复获取行,直到 net_buffer 写满,调用网络接口发出去。
三、若是发送成功,就清空 net_buffer,而后继续取下一行,并写入 net_buffer。
四、若是发送函数返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地网络栈(socket send buffer)写满了,进入等待。直到网络栈从新可写,再继续发送。
从上面的流程能够知道,若是一次要发送的数据量超过 socket send buffer 空间,那么就会拆分开来发送,并不会发生 " 内存打爆 " 的状况。由此咱们能够知道,MySQL 是边读边发的。
一、若是请求返回的数据量很大,那么在等待返回的过程当中使用 show processlist 查看 State 列的值就会为 " Sending to client",表示服务器端的网络栈写满了。
这是由于 Sate 列值的变化是在查询请求到达开始执行就会变为 " Sending data ",若是网络栈写满发就会切换为 " Sending to client ",表示 " 正在等待客户端接收结果 "。" Sending data " 可能处于线程执行过程当中的任意阶段,好比由于锁而阻塞的场景。
二、若是 show processlist 的 State 列一直为 " Sending to Client ",那么能够
1)查看这条SQL,判断是否能够优化,减小返回值。
2)将 net_buffer_length 设的大一些,来避免或者减小发送阻塞的时间。
在开始客户端会建立线程去链接服务器端,而后接收服务端返回的数据,客户端接收服务器端返回的数据有两种方式:
一、本地缓存。在本地开一片内存,先把结果存起来。若是用 API 开发,对应的就是 mysql_store_result 方法。建议在客户端处理量大时使用本地缓存。可使用 mysql -h$host -P$port -u$user -p$pwd -e "select * from db1.t" > $target_file 将返回的数据保存到指定文件。
二、不缓存,读一个处理一个。若是用 API 开发,对应的就是 mysql_use_result 方法。
回到上面的问题,为何使用 -quick 可能会致使客户端性能降低?这是由于客户端默认使用缓存来接收,因此在客户端正在处理其余数据时就能够先进行缓存,等到后面直接读取缓存就能够了。而使用 quick 就会使客户端接收不使用缓存,那么若是客户端正在执行其余操做这个数据就会被阻塞,而且服务器端对应的线程也会由于没有收到客户端的反馈而没有中断此次事务,此次事务涉及到的资源锁也没有释放,形成并发问题,影响效率。除此以外, quick 还有三个效果。
一、就是前面提到的,跳过表名自动补全功能。
二、客户端接收数据使用不缓存的方式。而 mysql_store_result 方法须要申请本地内存来缓存查询结果,若是查询结果太大,会耗费较多的本地内存,可能会影响客户端本地机器的性能;
三、不会把执行命令记录到本地的命令历史文件。