在MySQL中有两个kill命令:一个是kill query +线程id,表示终止这个线程中正在执行的语句;一个是kill connection +线程id,这里connection可缺省,表示断开这个线程的连接,当然如果这个线程有语句正在执行,也是要先停止正在执行的语句的。

kill并不是马上停止的意思,而是告诉执行线程说,这条语句已经不需要继续执行了,可以开始“执行停止的逻辑了”,例如释放锁。

当用户执行kill query thread_id_B时,MySQL里处理kill命令的线程做了两件事:

1. 把session B的运行状态改成THD::KILL_QUERY(将变量killed赋值为THD::KILL_QUERY);

2. 给session B的执行线程发一个信号。

第一步把状态置为THD::KILL_QUERY,但是可能线程当时正在等待锁之类的无法判断的状态的,所以就要发出一条信号。上面的分析中,隐含了这么三层意思:

1. 一个语句执行过程中有多处埋点,在这些埋点的地方判断线程状态,如果发现线程状态是THD::KILL_QUERY,才开始进入语句终止逻辑;

2. 如果处于等待状态,必须是一个可以被唤醒的等待,否则根本不会执行到埋点处;

3. 语句从开始进入终止逻辑,到终止逻辑完全完成,是有一个过程的。

线程没有执行到判断线程状态的逻辑

我们来看一个例子:

可以看到:

1. sesssion C执行的时候被堵住了;

2. 但是session D执行的kill query C命令却没什么效果,

3. 直到session E执行了kill connection命令,才断开了session C的连接,提示“Lost connection to MySQL server during query”

4. 但是这时候,如果在session E中执行show processlist,你就能看到下面这个图。

id=12这个线程的Commnad列显示的是Killed。客户端虽然断开了连接,但实际上服务端上这条语句还在执行过程中。

         在实现上,等行锁时,使用的是pthread_cond_timedwait函数,这个等待状态可以被唤醒。但是,在这个例子里,12号线程的等待逻辑是这样的:每10毫秒判断一下是否可以进入InnoDB执行,如果不行,就调用nanosleep函数进入sleep状态。

虽然12号线程的状态已经被设置成了KILL_QUERY,但是在这个等待进入InnoDB的循环过程中,并没有去判断线程的状态,因此根本不会进入终止逻辑阶段。而当session E执行kill connection 命令时,是这么做的,

1. 把12号线程状态设置为KILL_CONNECTION;

2. 关掉12号线程的网络连接。因为有这个操作,所以你会看到,这时候session C收到了断开连接的提示。

 

终止逻辑耗时较长

1. 超大事务执行期间被kill。这时候,回滚操作需要对事务执行期间生成的所有新数据版本做回收操作,耗时很长。

2. 大查询回滚。如果查询过程中生成了比较大的临时文件,加上此时文件系统压力大,删除临时文件可能需要等待IO资源,导致耗时较长。

3. DDL命令执行到最后阶段,如果被kill,需要删除中间过程的临时文件,也可能受IO资源影响耗时较久。

 

Ctril+C命令

执行Ctrl+C的时候,是MySQL客户端另外启动一个连接,然后发送一个kill query 命令。

 

表多连接就慢吗?

当使用默认参数连接的时候,MySQL客户端会提供一个本地库名和表名补全(使用Tab键)的功能。为了实现这个功能,客户端在连接成功后,需要多做一些操作:

1. 执行show databases

2. 切到db1库,执行show tables

3. 把这两个命令的结果用于构建一个本地的哈希表。

上面的第三步,是很花时间的,加上命令的-A可以跳过这个功能。加上–quick也可以跳过这个阶段。但是,–quick有一个问题,先看看mySQL客户端发送请求后,接收服务端返回结果的两种方式:

1. 一种是本地缓存,也就是在本地开一片内存,先把结果存起来。如果你用API开发,对应的就是mysql_store_result 方法。

2. 另一种是不缓存,读一个处理一个。如果你用API开发,对应的就是mysql_use_result方法。

       加上–quick参数,就会使用第二种不缓存的方式,这时如果本地处理得慢,就会导致服务端发送结果被阻塞,因此会让服务端变慢。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐