MySQL 客户端 Ctrl + C,服务端会发生什么?

我们也许有过这样的经历:用 mysql​ 客户端连上数据库,执行一条 SQL,结果迟迟执行不完,我们等得不耐烦了,顺手就是一个 Ctrl + C。

Ctrl + C 之后,客户端会干什么,服务端又会发生什么?我们一起来看看。

本文内容基于 MySQL 8.0.32 源码,涉及存储引擎为 InnoDB。

1、客户端会干什么?

想要观察 Ctrl + C 时,客户端会干什么,用 mysql 连接数据库时可以指定 -v 参数,如下:

mysql -h127.0.0.1 -uroot -v

连上数据库之后,执行一条 SQL(以 UPDATE 为例​)。SQL 执行完成之前,在键盘上按下 Ctrl + C,如下:

注意:没有使用 begin 显式开启事务,且系统变量 autocommit 的值为 ON。

mysql>UPDATEt1SETblob1=REPEAT("这是 blob2 字段", 10240);
--------------
UPDATEt1SETblob1=REPEAT("这是 blob2 字段", 10240)
--------------

--客户端发送KILLQUERY给服务端之后
--输出的提示信息
^C^C--sending"KILL QUERY 11"toserver ...

#服务端执行KILLQUERY之后
#客户端自己的输出信息
^C--queryaborted

--服务端返回给客户端的信息
ERROR1317 (70100): Queryexecutionwasinterrupted

从以上输出可以看到,客户端 Ctrl + C,实际上是给服务端发出了一条 KILL QUERY 命令。

这和我们手动执行 KILL QUERY 命令是一样的,接下来,我们就来看看服务端是怎么执行 KILL QUERY 命令的。

2、KILL QUERY

在 KILL QUERY 命令之前,客户端已经发出了一条 Update SQL,服务端分配了一个线程,正在执行 Update SQL。

Update SQL 还没执行完,客户端 Ctrl + C 又发出了 KILL QUERY 命令,服务端收到命令之后,会调度另一个线程来执行 KILL QUERY 命令。

为了方便介绍,我们把执行 Update SQL 的线程称为 Update 线程​,执行 KILL QUERY 命令的线程称为 Kill 线程​。注意:MySQL 内部是不做这样区分的。

KILL QUERY 命令的执行流程如下:

第 1 步,Kill 线程根据 query id​ 查找 Update 线程。如果没有找到​,KILL QUERY 命令执行结束;如果找到了,进入第 2 步。

query id​ 是 show processlist 执行结果中的 id 字段。

第 2 步,Kill 线程判断当前连接的 MySQL 用户是否有权限干掉 Update 线程。如果没有​权限,KILL QUERY 命令执行结束;如果有权限,进入第 3 步。

第 3 步,判断 Update 线程是否正在读写数据字典表。

如果不是​,Kill 线程继续执行第 4 ~ 6 步;如果是,Kill 线程的使命就到此结束了,接力棒交给 Update 线程。

Update 线程​读写数据字典表结束,就会马上开始执行 KILL QUERY 命令的第 3 ~ 6 步。

这种情况下,第 3 步会被执行 2 次(Kill 线程和 Update 线程各执行一次)。

第 4 步,把 Update 线程的 killed​ 属性设置为 KILL_QUERY​,此时,Update 线程处于被标记为将要被干掉,但是还没有被干掉的状态。

这一步可以想象成城市建设过程中,在要拆迁的房子上写了个大大的拆字,但是房子还立在那里。

第 5 步,如果 Update 线程正在等待获取存储引擎中的锁,则放弃等待;如果 Update 线程已经持有存储引擎中的锁,则释放锁。

第 6 步,判断 Update 线程是否持有某个条件变量​(保存在 current_cond)中。

如果持有,发送广播通知正在等待这个条件变量的其它线程,告诉它们可以继续执行了。

通过前面的介绍,我们可以看到:不管是 Kill 线程,还是 Update 线程自己执行​第 3 ~ 6 步​,都只是给 Update 线程打上了 KILL_QUERY 标记,而没有直接把 Update 线程干掉。

Update 线程是怎么被干掉的呢?请继续往下看。

3、自己把自己干掉

KILL QUERY 执行过程中,为什么不直接把 Update 线程干掉?

不是不想,而是不能。

因为线程不管执行什么操作,都需要进行收尾工作,做到有始有终。

如果 Update 线程直接被干掉,就来不及进行收尾工作,例如:已经申请的内存无法释放,会导致内存泄漏。

所以,想要妥善干掉一个线程,需要即将被干掉的线程主动配合 Kill 线程才行。

妥善干掉一个 Update 线程的场景是这样的:

Kill 线程对 Update 线程说:我要把你干掉。

Update 线程回答:不劳你动手,我自己来。

MySQL 让这个场景变成现实的方式,是在代码中的各个角落进行埋点,埋点逻辑:判断当前线程是否被打上了 KILL_QUERY 标记,如果​是,则中断正在执行的操作,进入收尾阶段。

举个例子:

// sql/sql_update.cc
// 以下代码处理更新单表的 SQL,例如:
// update t1 set i1 = 100
boolSql_cmd_update::update_single_table(THD*thd) {
...
while (true) {
// 从存储引擎读取一条记录
error=iterator->Read();
// 如果读取出错(error)
// 或者 thd->killed 不等于 0(也就是 true)
// 对应本文的场景是:线程被打上了 KILL_QUERY 标记
// 直接结束循环
if (error||thd->killed) break;
...
}
...
}

从以上代码可以看到,执行 Update 操作过程中,如果发现读取出错(对应本文场景是 Update 线程被打上了 KILL_QUERY 标记),直接 break 退出循环,中断执行。

4、回滚

Update 线程执行过程中,事务有可能已经增、删、改了一些数据,中断正在执行的操作之后,事务是需要回滚的。

当 Update 线程的执行流程回到 mysql_execute_command():

intmysql_execute_command(THD*thd, boolfirst_level) {
...
if ((thd->is_error() &&!early_error_on_rep_command) ||
(thd->variables.option_bits&OPTION_MASTER_SQL_ERROR))
trans_/opt/data/workspace_c/mysql8/sql/sql_class.ccrollback_stmt(thd);
else {
/* If commit fails, we should be able to reset the OK status. */
thd->get_stmt_da()->set_overwrite_status(true);
trans_commit_stmt(thd);
thd->get_stmt_da()->set_overwrite_status(false);
}
...
}

从代码中可以看到,thd->is_error() 返回 true,说明事务执行过程中出现了错误,对应到本文的场景,就是事务被 KILL QUERY 中断了,会执行 trans_rollback_stmt(thd),回滚事务。

只有在开启组复制(GROUP REPLICATION)过程中出现错误时,early_error_on_rep_command 才有可能被设置为 true,这里我们先忽略。

到这里,KILL QUERY 就算是基本介绍完了。

之所以说基本介绍完了,是因为还留有一点点尾巴。

前面我们介绍过,Update 线程执行到埋点的时候,如果判断自己已经被标记为即将被干掉,就会中断执行。

但是,还有一种很小的可能性,就是 Update 线程执行过程中,已经经过了所有埋点之后,才被标记为即将被干掉,Update 线程也就没有机会中断执行了。

这种情况下,就会进入以上代码中的 else 分支,执行 trans_commit_stmt(thd),提交事务。

鉴于进入 else 分支提交事务的可能性很小,我们可以认为只要客户端 Ctrl + C,Update 线程就会中断执行,并回滚事务。

5、总结

客户端连接上 MySQL 之后,给服务端发送一条 SQL,SQL 执行完成之前,客户端 Ctrl + C,实际上会给服务端发送一条 KILL QUERY 命令,和我们手动执行 kill query <query_id> 的效果是一样的。

服务端会分配一个空闲线程(Kill 线程)执行 kill query 操作,给 Update 线程打上 KILL_QUERY 标记。

如果即将被干掉的线程(Update 线程)正在读写数据字典表,它会从 kill 线程手上接过接力棒,给自己打上 KILL_QUERY 标记。

Update 线程发现自己被打上了 KILL_QUERY 标记,就会中断执行,在 mysql_execute_command() 方法中,会回滚事务。

有一点需要说明,前面只是以 Update SQL 为例来介绍 KILL QUERY,其它 SQL 的 KILL QUERY 流程也是一样的。​

6、番外篇

前面 1 ~ 5 小节介绍的是没有通过 begin 语句显式开启事务,并且系统变量 autocommit 的值是 ON 的场景。

如果通过 begin 显式开启了事务,或者把系统变量 autocommit 的值设置为 OFF,前面 1 ~ 5 小节介绍的内容也是适用的,但是会有一点区别:

4.回滚小节只能作用于事务中的一条 SQL,而不会影响整个事务。至于整个事务是提交还是回滚,取决于我们会给服务端发送 commit 还是 rollback 语句。

本文转载自微信公众号「一树一溪」,可以通过以下二维码关注。转载本文请联系一树一溪公众号。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/244165.html<

(0)
管理的头像管理
上一篇2025-04-25 14:44
下一篇 2025-04-25 14:45

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注