(18)MySQL的kill命令与连接服务器慢

在 MySQL 中有两个 kill 命令:

一个是 kill query + 线程 id ,表示终止这个线程中正在执行的语句;一个是 kill connection + 线程 id ,这里 connection 可缺省,表示断开这个线程的连接

但是:使用了kill命令,没能断开这个连接。再执行 show processlist 命令,看到这条语句的 Command 列显示的是 Killed

其实大多数情况下, kill query/connection 命令是有效的:比如,执行一个查询的过程中,发现执行时间太久,要放弃继续查询,这时我们就可以用 kill query 命令,终止这条查询语句。或者语句处于锁等待的时候,直接使用 kill 命令也是有效的

一、收到kill后

                                                  (18)MySQL的kill命令与连接服务器慢_第1张图片

 session C  执行 kill query 以后, session B 几乎同时就提示了语句被中断。但是 session B虽然处于 blocked 状态,但还是拿着一个 MDL 读锁的。如果线程被 kill 的时候,就直接终止,那之后这个 MDL 读锁就没机会被释放了。

这样看来, kill 并不是马上停止的意思,而是告诉执行线程说,这条语句已经不需要继续执行了,开始“执行停止的逻辑”

其实,这跟 Linux 的 kill 命令类似, kill -N pid 并不是让进程直接停止,而是给进程发一个信号,然后进程处理这个信号,进入终止逻辑。只是对于 MySQL 的 kill 命令来说,不需要传信号量参数,就只有 “ 停止 ” 这个命令

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

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

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

为什么要发信号呢?由上面的例子,session B 处于锁等待状态,如果只是把 session B 的线程状态设置THD::KILL_QUERY ,线程 B 并不知道这个状态变化,还是会继续等待。发一个信号的目的,就是让 session B 退出等待,来处理这个 THD::KILL_QUERY 状态

上面的分析中,隐含了这么三层意思:

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

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

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

 

二、kill不掉的例子

首先,执行 set global innodb_thread_concurrency=2 ,将 InnoDB 的并发线程上限数设置为 2 ;然后,执行下面的序列

                                                         (18)MySQL的kill命令与连接服务器慢_第2张图片

可以看到:
1. sesssion C 执行的时候被堵住了;
2.  但是 session D 执行的 kill query C 命令却没什么效果
3.  直到 session E 执行了 kill connection 命令,才断开了 session C 的连接,提示 “Lostconnection to MySQL server during query” ,

4.  但是这时候,如果在 session E 中执行 show processlist ,可以看到这个id=12的线程显示的killed,也就是说,客户端虽然断开了连接,但实际上服务端上这条语句还在执行过程中

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

也就是说,虽然 12 号线程的状态已经被设置成了 KILL_QUERY,但是在这个等待进入 InnoDB 的循环过程中,并没有去判断线程的状态,因此根本不会进入终止逻辑阶段,也就是没有像上面我们说的,在 “ 埋点 ” 的地方判断线程状态,因为根本还没有进入innoDB(因为之前我们并发线程上线的设定)

而 session E 执行 kill connection  命令时,是这么做的,
1.  把 12 号线程状态设置为 KILL_CONNECTION ;
2.  关掉 12 号线程的网络连接。因为有这个操作,所以你会看到,这时候 session C 收到了断开连接的提示。

而因为在执行 show processlist 的时候,有一个特别的逻辑:

如果一个线程的状态是 KILL_CONNECTION ,就把 Command 列显示成 Killed

所以其实,即使是客户端退出了,这个线程的状态仍然是在等待中。只有等到满足进入 InnoDB 的条件后, session C 的查询语句继续执行,然后才有可能判断到线程状态已经变成了 KILL_QUERY 或者 KILL_CONNECTION ,再进入终止逻辑阶段。

小结:这个例子是 kill 无效的第一类情况,即:线程没有执行到判断线程状态的逻辑。

第二类情况:终止逻辑耗时较长。这时候,从 show processlist 结果上看也是Command=Killed ,需要等到终止逻辑完成,语句才算真正完成。这类情况,比较常见的场景有以下几种:

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

2.大查询回滚,删除临时文件可能需要等待 IO 资源,导致耗时较长

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

这些 “kill 不掉 ” 的情况,其实是因为发送 kill 命令的客户端,并没有强行停止目标线程的执行,而只是设置了个状态,并唤醒对应的线程而被 kill 的线程,需要执行到判断状态的 “ 埋点 ” ,才会开始进入终止逻辑阶段。并且,终止逻辑本身也是需要耗费时间的。

 

如果直接在客户端通过 Ctrl+C 命令,是不是就可以直接终止线程呢?不能,在客户端的操作只能操作到客户端的线程客户端和服务端只能通过网络交互,是不可能直接操作服务端线程的。

三、另外两个关于客户端的误解

第一个误解是:如果库里面的表特别多,连接就会很慢

每个客户端在和服务端建立连接的时候,需要做的事情就是 TCP 握手、用户校验、获取权限。但这几个操作,显然跟库里面表的个数无关。

(18)MySQL的kill命令与连接服务器慢_第3张图片

当使用默认参数连接的时候, MySQL 客户端会提供一个本地库名和表名补全(输入前缀,使用tab可以可自动补全表名或提示)的功能。为了实现这个功能,客户端在连接成功后,需要多做一些操作:

1.  执行 show databases ;
2.  切到 db1 库,执行 show tables ;
3.  把这两个命令的结果用于构建一个本地的哈希表。当一个库中的表个数非常多的时候,这一步就会花比较长的时间。

这些操作中,最花时间的就是第三步在本地构建哈希表的操作。我们感知到的连接过程慢,其实并不是连接慢,也不是服务端慢,而是客户端慢。

如果在连接命令中加上 -A ,就可以关掉这个自动补全的功能。除了加 -A 以外,加 –quick( 或者简写为 -q) 参数,也可以跳过这个阶段。但是,这个 –quick 是一个更容易引起误会的参数,也是关于客户端常见的一个误解。设置了这个参数可能会降低服务端的性能

第二个误解:加 –quick( 或者简写为 -q) 参数让服务端加速

MySQL 客户端发送请求后,接收服务端返回结果的方式有两种:
1.  一种是本地缓存,也就是在本地开一片内存,先把结果存起来。如果你用 API 开发,对应的就是 mysql_store_result  方法。
2.  另一种是不缓存,读一个处理一个。如果你用 API 开发,对应的就是 mysql_use_result 方法。

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

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

但是为什么这个参数取名叫作 quick 呢?因为它可以让客户端变快

  • 第一点,跳过表名自动补全功能
  • 不会把执行命令记录到本地的命令历史文件
  •  mysql_store_result 需要申请本地内存来缓存查询结果,可能会影响会影响客户端本地机器的性能

 

 

你可能感兴趣的:((18)MySQL的kill命令与连接服务器慢)