kill 命令之后MySQL都做了哪些

·  阅读 37
kill 命令之后MySQL都做了哪些

微信公众号【欢少的成长之路】

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

大家好,我是Leo。目前在常州从事Java后端开发的工作。上一篇我们介绍了线上数据库误删数据后,到底是跑路还是该如何解决!这一篇我们介绍一下为什么我们在调试SQL的时候会出现Kill不掉线程的情况。

思路

本篇文章的介绍思路以下图的思维导图为大纲。也有利于读者更好的分辨可读性!

为什么需要kill

测试

从测试方面考虑的话,就是我们在写SQL语句的时候。开始寻找不足点,有些时候会偶然间执行一个大事务,这个时候我们为了提升效率往往会直接干到这个查询

生产

随着数据量爆发式的增长,一些查询功能越来越慢的。而我们在调试一个功能的时候往往就是调试背后的SQL语句。还有一种情况就是,修改表结构的时候,由于数据量过大,我们会放弃普遍方法,会寻找一个快速的方法。

锁等待

比如发生死锁的时候,或者两个锁在争锁的时候。往往会需要kill。结束掉一个事务给事务回滚放行另一个事务

kill内部都做了啥

首先介绍两种kill写法吧

  • kill query + 线程 id:表示终止这个线程中正在执行的语句
  • kill connection + 线程 id:表示断开这个线程的连接,当然如果这个线程有语句正在执行,也是要先停止正在执行的语句的。

言归正传

前几篇文章我们介绍过,对一个表进行增删改查的时候,会在表上加一个读锁。这个时候用户虽然处于blocked状态,但是还拿着MDL读锁。如果线程直接被kill的话,读锁就没办法释放了。所以最理想的状态应该是,kill之后,让他做一些收尾工作,全部结束之后再结束掉线程。

那么它到底做了啥

  1. 把 session B 的运行状态改成 THD::KILL_QUERY(将变量 killed 赋值为 THD::KILL_QUERY);
  2. 给 session B 的执行线程发一个信号。

流程扩展

我们继续按照上述的流程作一个扩展。为什么要发信号呢?

举一个多用户请求的例子。如果用户A处于锁等待状态,如果只是把用户A的线程状态设置为THD::KILL_QUERY ,线程A并不知道这个状态变化,还是会继续等待。发一个信号的目的,就是让 用户A 退出等待,来处理这个 THD::KILL_QUERY 状态。

换言之。如果发现线程状态是THD::KILL_QUERY 才开始进入语句终止逻辑。

kill不掉是啥鬼

首先我们介绍一个参数,控制线程并发上限的这个参数innodb_thread_concurrency 举例说明一下

  • sessionA:select sleep(100) from t
  • sessionB:select sleep(100) from t
  • sessionC:select sleep(100) from t (blocked)
  • sessionD:kill query C
  • sessionE:kill C <=> kill connection C

通过上述5个用户的执行,我们先把他设置成set global innodb_thread_concurrency=2 可以看到

  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,你就能看到下面这个图。

img

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

为什么在执行 kill query 命令时,不像 update 语句一样退出呢?

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

也就是说,虽然 12 号线程的状态已经被设置成了 KILL_QUERY,但是在这个等待进入 InnoDB 的循环过程中,并没有去判断线程的状态,因此根本不会进入终止逻辑阶段。

而当 session E 执行 kill connection 命令时

  • 把 12 号线程状态设置为 KILL_CONNECTION;
  • 关掉 12 号线程的网络连接。因为有这个操作,所以你会看到,这时候 session C 收到了断开连接的提示。

show processlist 隐藏逻辑 如果一个线程的状态是KILL_CONNECTION,就把Command列显示成Killed。

所以在show processlist列表中会把connection改成killed,也就是我们看到的状态。

综上所述 kill无效的几种情况:

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

跟这种情况相同的,还有由于 IO 压力过大,读写 IO 的函数一直无法返回,导致不能及时判断线程的状态。

  • 终止逻辑耗时较长

从 show processlist 结果上看也是 Command=Killed,需要等到终止逻辑完成,语句才算真正完成。比如超大事务被kill需要做很多回收查找,大查询回滚,DDL命令。

我们可以对大查询回滚做一个扩展介绍,前几篇文章我们介绍过查询数据的时候,当数据量超过一个数量的时候我们会采用硬盘存储,如果小于这个数量的时候会采用内存存储。所以如果在磁盘中查询的时候。进行回滚!相应的磁盘页也会消耗一定的时间去关闭释放。

客户端与服务端的关闭问题

Ctrl+C 关闭的什么

客户端的操作只能操作到客户端的线程,客户端和服务端只能通过网络交互,是不可能直接操作服务端线程的。而由于 MySQL 是停等协议,所以这个线程执行的语句还没有返回的时候,再往这个连接里面继续发命令也是没有用的。实际上,执行 Ctrl+C 的时候,是 MySQL 客户端另外启动一个连接,然后发送一个 kill query 命令。

所以,你可别以为在客户端执行完 Ctrl+C 就万事大吉了。因为,要 kill 掉一个线程,还涉及到后端的很多操作。

数据表过多会影响性能吗

我们在第一篇文章就介绍了每个客户端和服务器建立连接时,做了哪些事情。比如TCP握手,用户校验,获取权限等

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

  1. 执行 show databases;
  2. 切到 db1 库,执行 show tables;
  3. 把这两个命令的结果用于构建一个本地的哈希表。

最耗时的也就是第三步的哈希表构建了,也就是说,我们感知到的连接过程慢,其实并不是连接慢,也不是服务端慢,而是客户端慢

-A,-quick 可以跳过这个阶段。为什么这么说呢,我们可以介绍一下 -quick参数涉及的MySQL配置。

MySQL 客户端发送请求后,接收服务端返回结果的方式有两种:

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

MySQL 客户端默认采用第一种方式,而如果加上–quick 参数,就会使用第二种不缓存的方式。

采用不缓存的方式时,如果本地处理得慢,就会导致服务端发送结果被阻塞,因此会让服务端变慢

扩展

为什么取名为quick呢?

  • 第一点,就是前面提到的,跳过表名自动补全功能。
  • 第二点,mysql_store_result 需要申请本地内存来缓存查询结果,如果查询结果太大,会耗费较多的本地内存,可能会影响客户端本地机器的性能;
  • 第三点,是不会把执行命令记录到本地的命令历史文件。

综上所述:quick是提升客户端的性能,不是提升服务端的性能

总结

今天大概介绍了MySQL内部在kill线程的时候,都做了哪些操作以及强化了最开始的文章深度。

这里的文章深度主要是在哈希表构建的那一段。

下一篇文章 更新一篇字节,网易,阿里,腾讯,美团,快手面试系列的MySQL文章<一>

分类:
后端
标签:
分类:
后端
标签: