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是让客户端变得更快,而不是让服务端变得更快。

相关推荐
我不是混子10 小时前
什么是MySQL的回表?
后端·mysql
我不是混子10 小时前
为什么不建议使用SELECT * ?
后端·mysql
AAA修煤气灶刘哥1 天前
数据库优化自救指南:从SQL祖传代码到分库分表的骚操作
数据库·后端·mysql
RestCloud1 天前
MySQL分库分表迁移:ETL平台如何实现数据合并与聚合
mysql·api
咖啡Beans1 天前
MySQL中使用@符号定义用户变量
数据库·mysql
知其然亦知其所以然1 天前
MySQL 社招必考题:如何优化特定类型的查询语句?
后端·mysql·面试
粘豆煮包1 天前
掀起你的盖头来之《数据库揭秘》-3-SQL 核心技能速成笔记-查询、过滤、排序、分组等
后端·mysql
DemonAvenger1 天前
MySQL海量数据快速导入导出技巧:从实战到优化
数据库·mysql·性能优化
程序新视界2 天前
MySQL中什么是回表查询,如何避免和优化?
mysql