MySQL 32 为什么还有kill不掉的语句?

MySQL有两个kill命令:

  • kill query+线程id,表示终止该线程正在执行的语句;

  • kill (connection)+线程id,表示断开这个线程的连接,如果线程有语句正在执行,会先停止正在执行的语句。

有时候可能会遇到:使用了kill,却没能断开该连接,再执行show processlist时,看到这条语句的command列显示的是killed。

那这是什么意思呢?不是应该直接在show processlist结果里看不到这个线程了吗?本文就来讨论该问题。

收到kill后,线程做什么?

比如有一个场景:

session C执行kill query后,session B几乎同时提示了语句被中断,这是预期结果。那么session B是直接终止掉线程,什么都不管直接退出吗?

不是的,session在处于blocked状态时,还是拿着一个MDL读锁的,如果kill时直接终止,该读锁就没机会释放。这样看的话,kill并不是马上停止的意思,而是告诉执行线程该语句已经不需要继续执行,可以开始"执行停止逻辑"了。

当session C执行kill语句,MySQL里处理kill命令的线程会做两件事:

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

  • 给session B的执行线程发一个信号,目的是让session B退出锁等待,来处理THD::KILL_QUERY状态。

上面的分析隐含了一些意思:

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

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

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

接下来看一个kill不掉的例子。首先执行set global innodb_thread_concurrency=2,将InnoDB并发线程上限数设置为2,然后执行下面的序列:

  • session C执行时blocked;

  • session D的kill query C没产生效果;

  • session E执行kill connection,才断开session C的连接;

  • 但若此时在session E执行show processlist,结果如下:

此时id=12的command列显示killed,说明客户端虽然断开了连接,但实际服务端上这条语句还在执行过程中。

这里为什么和第一个例子不同呢?

在实现上,等待行锁使用的是pthread_cond_timedwait函数,该等待状态可以被唤醒。但这里12号线程的等待逻辑是每10毫秒判断是否可以进入InnoDB执行,如果不行就调用nanosleep函数进入sleep状态。也就是说,虽然12号线程状态已被设置为KILL_QUERY,但在等待进入InnoDB循环的过程中,并未判断线程的状态,因此不会进入终止逻辑阶段。

当session E执行kill connection时:

  • 将12号线程状态设置为KILL_CONNECTION;

  • 关掉12号线程的网络连接,因此session C会收到断开连接的提示。

那为什么show processlist时能看到command显示killed呢?是因为执行show processlist时有一个特别的逻辑:如果一个线程的状态是KILL_CONNECTION,就把command列显示为killed。所以即使客户端退出,该线程的状态仍然是在等待中。

只有等到满足进入InnoDB的条件后,session C的查询语句继续执行,然后才有可能判断到线程状态已经变成KILL_QUERY或KILL_CONNECTION,再进入终止逻辑阶段,线程才会退出。

该例子是kill无效的第一类情况,即线程没有执行到判断线程状态的逻辑。相同情况的还有因为IO压力过大,读写IO的函数一直无法返回,导致不能及时判断线程状态。

另一类情况是终止逻辑耗时较长,这时从show processlist结果上看也是command=killed,需要等到终止逻辑完成,语句才算真正完成,这类情况常见场景有以下几种:

  • 超大事务执行期间被kill,此时回滚需要对事务执行期间所有新数据版本做回收,耗时很长;

  • 大查询回滚,删除查询过程生成的大临时文件,加上此时文件系统压力大,该过程可能需要等待IO资源,导致耗时很长;

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

关于客户端的误解

第一个误解是,如果直接在客户端Ctrl+C,是否可以直接终止线程呢?

答案是不可以的,在客户端的操作只能操作到客户端的线程,而客户端和服务端只能通过网络交互,是不可能直接操作服务端线程的。实际执行Ctrl+C时,是MySQL客户端另外启动一个连接,然后发送一个kill query命令。

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

很多人会认为是表的数目影响了连接性能,但从第一篇文章就知道,客户端和服务端建立连接的时候,需要做的就是TCP握手、用户校验、获取权限,这些操作跟表的个数无关。实际上,当使用默认参数连接时,MySQL客户端会提供本地库名和表名补全的功能,为实现这个功能,客户端连接成功后,需要多做一些操作:

  • 执行show databases;

  • 切到库执行show tables;

  • 将这两个命令的结果用于构建一个本地的哈希表。

当一个库中表个数非常多,第三步就会花很长时间。因此,感知到的连接过程慢,不是连接慢和服务端慢,而是客户端慢。

这里自动补全效果是在输入库名或表名时,输入前缀,可以使用Tab补全。在连接命令中加上-A,可以关掉这个自动补全功能,因此如果自动补全用得不多,建议关掉。

另外,除了加-A,加-quick(简写-q),也可以跳过这个阶段。

第三个误解,就是关于-quick这个参数。

从字面上看,会觉得这是一个让服务端加速的参数,但实际上设置这个参数可能会降低服务端的性能。

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

  • 一种是本地缓存,即在本地开一片内存,先存结果;

  • 另一种是不缓存,读一个处理一个。

MySQL客户端默认采用第一种,加上-quick后就会使用第二种。此时如果本地处理得慢,就会导致服务端发送结果被阻塞,因此会让服务端变慢。

因此,-quick是让客户端变得更快,而不是让服务端变得更快。

相关推荐
Zzzzmo_5 小时前
【MySQL】JDBC(含settings.xml文件配置/配置国内镜像以及pom.xml文件修改)
数据库·mysql
FirstFrost --sy6 小时前
MySQL内置函数
数据库·mysql
eggwyw6 小时前
MySQL-练习-数据汇总-CASE WHEN
数据库·mysql
mygljx9 小时前
MySQL 数据库连接池爆满问题排查与解决
android·数据库·mysql
Bdygsl10 小时前
MySQL(1)—— 基本概念和操作
数据库·mysql
身如柳絮随风扬10 小时前
什么是左匹配规则?
数据库·sql·mysql
jiankeljx10 小时前
mysql之如何获知版本
数据库·mysql
小李来了!11 小时前
数据库DDL、DML、DQL、DCL详解
数据库·mysql
我科绝伦(Huanhuan Zhou)12 小时前
【生产案例】MySQL InnoDB 数据损坏崩溃修复
数据库·mysql·adb
海棠蚀omo12 小时前
从零敲开 MySQL 的大门:库与表的基础操作实战(保姆级入门指南)
数据库·mysql