一、kill语句:
1、kill语句分类:
- kill query +线程id,表示终止这个线程中正在执行的语句;
- kill connection +线程id,这里connection可缺省,表示断开这个线程的连接,当然如果这个线程有语句正在执行,也是要先停止正在执行的语句的。
二、收到kill语句之后,线程的反应:
1、当用户执行kill query thread_id_B时,MySQL里处理kill命令的线程做了两件事:
- 把session B的运行状态改成THD::KILL_QUERY(将变量killed赋值为THD::KILL_QUERY);
- 给session B的执行线程发一个信号。
2、发信号的原因:
session B处于锁等待状态,如果只是把session B的线程状态设置THD::KILL_QUERY,线程B并不知道这个状态变化,还是会继续等待。发一个信号的目的,就是让session B退出等待,来处理这个THD::KILL_QUERY状态。
3、总结:
- 一个语句执行过程中有多处“埋点”,在这些“埋点”的地方判断线程状态,如果发现线程状态是THD::KILL_QUERY,才开始进入语句终止逻辑;
- 如果处于等待状态,必须是一个可以被唤醒的等待,否则根本不会执行到“埋点”处;
- 语句从开始进入终止逻辑,到终止逻辑完全完成,是有一个过程的。
三、某些线程kill不掉的原因:
1、线程没有执行到判断线程状态的逻辑:
首先,执行set global innodb_thread_concurrency=2,将InnoDB的并发线程上限数设置为2;然后,执行下面的序列:
<1>、此时,可以看出:
- sesssion C执行的时候被堵住了;
- 但是session D执行的kill query C命令却没什么效果,
- 直到session E执行了kill connection命令,才断开了session C的连接,提示“Lost connection to MySQL server during query”。
但是这时候,如果在session E中执行show processlist,会发现:
此时id=12这个线程的Commnad列显示的是Killed。也就是说,客户端虽然断开了连接,但实际上服务端上这条语句还在执行过程中。
<2>、执行kill query命令时,这条语句退出不了的原因:
在实现上,等行锁时,使用的是pthread_cond_timedwait函数,这个等待状态可以被唤醒。但是,在这个例子里,12号线程的等待逻辑是这样的:每10毫秒判断一下是否可以进入InnoDB执行,如果不行,就调用nanosleep函数进入sleep状态。也就是说,虽然12号线程的状态已经被设置成了KILL_QUERY,但是在等待进入InnoDB的循环过程中,并没有去判断线程的状态,因此根本不会进入终止逻辑阶段。
<3>、sessionE的执行逻辑:
- 把12号线程状态设置为KILL_CONNECTION;
- 关掉12号线程的网络连接。因为有这个操作,所以你会看到,这时候session C收到了断开连接的提示。
<4>、show processlist的时候,看到Command列显示为killed的原因:
因为在执行show processlist的时候,有一个特别的逻辑:
如果一个线程的状态是KILL_CONNECTION,就把Command列显示成Killed。
<5>、12号线程退出的时机:
只有等到满足进入InnoDB的条件后,session C的查询语句继续执行,然后才有可能判断到线程状态已经变成了KILL_QUERY或者KILL_CONNECTION,再进入终止逻辑阶段。
2、终止逻辑耗时较长:
<1>、分类:
- 超大事务执行期间被kill。这时候,回滚操作需要对事务执行期间生成的所有新数据版本做回收操作,耗时很长。
- 大查询回滚。如果查询过程中生成了比较大的临时文件,加上此时文件系统压力大,删除临时文件可能需要等待IO资源,导致耗时较长。
- DDL命令执行到最后阶段,如果被kill,需要删除中间过程的临时文件,也可能受IO资源影响耗时较久。
3、kill不了语句的原因:
- 线程没有执行到判断线程状态的逻辑。
- 由于IO压力过大,读写IO的函数一直无法返回,导致不能及时判断线程的状态。
- 终止逻辑耗时较长。这时候,从show processlist结果上看也是Command=Killed,需要等到终止逻辑完成,语句才算真正完成。
三、两个关于客户端的误解:
1、误解一:如果库里面的表特别多,连接就会很慢。
<1>、连接慢的原因:
每个客户端在和服务端建立连接的时候,需要做的事情就是TCP握手、用户校验、获取权限。但这几个操作,显然跟库里面表的个数无关。
-
当使用默认参数连接的时候,MySQL客户端会提供一个本地库名和表名补全的功能。为了实现这个功能,客户端在连接成功后,需要多做一些操作:
- 执行show databases;
- 切到db1库,执行show tables;
- 把这两个命令的结果用于构建一个本地的哈希表。
在这些操作中,最花时间的就是第三步在本地构建哈希表的操作。所以,当一个库中的表个数非常多的时候,这一步就会花比较长的时间。
所以,感知到的连接过程慢,其实并不是连接慢,也不是服务端慢,而是客户端慢。
<2>、解决办法:
连接命令中加上-A,就可以关掉这个自动补全的功能,然后客户端就可以快速返回了。这里自动补全的效果就是,在输入库名或者表名的时候,输入前缀,可以使用Tab键自动补全表名或者显示提示。实际使用中,如果自动补全功能用得并不多,建议每次使用的时候都默认加-A。
加–quick(或者简写为-q)参数,也可以达到相同目的。
2、误解二:–quick(或者简写为-q)参数是一个让服务端加速的参数。
<1>、MySQL客户端发送请求后,接收服务端返回结果的方式有两种:
- 一种是本地缓存,也就是在本地开一片内存,先把结果存起来。如果用API开发,对应的就是mysql_store_result 方法。
- 另一种是不缓存,读一个处理一个。如果用API开发,对应的就是mysql_use_result方法。
MySQL客户端默认采用第一种方式,而如果加上–quick参数,就会使用第二种不缓存的方式。采用不缓存的方式时,如果本地处理得慢,就会导致服务端发送结果被阻塞,因此会让服务端变慢。
<2>、这个参数可以达到的效果:
- 第一点,跳过表名自动补全功能。
- 第二点,mysql_store_result需要申请本地内存来缓存查询结果,如果查询结果太大,会耗费较多的本地内存,可能会影响客户端本地机器的性能;
- 第三点,是不会把执行命令记录到本地的命令历史文件。
所以–quick参数的意思,是让客户端变得更快。