我们已经准备好了,你呢?

2024我们与您携手共赢,为您的企业形象保驾护航!

来源:一树一溪(ID:)授权转载

作者:曹胜春

我们可能都有过这样的经历:使用 MySQL 客户端连接数据库,执行一条 SQL 语句,但执行时间很长,等不及了就直接按 Ctrl + C。

按下Ctrl+C之后,客户端会发生什么?服务器端又会发生什么?我们来看看。

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

目录

文本

1. 客户会做什么?

为了观察当按下Ctrl+C时客户端做了什么,可以在用mysql连接数据库时指定-v参数,如下:

mysql -h127.0.0.1 -uroot -v

连接数据库后,执行一条SQL语句(例如),在执行SQL语句前,先按下键盘上的Ctrl+C,如下:

注意:没有明确使用begin来启动事务,并且系统变量的值为ON。

mysql> UPDATE t1 SET blob1 = REPEAT("这是 blob2 字段"10240);
--------------
UPDATE t1 SET blob1 = REPEAT("这是 blob2 字段"10240)
--------------

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

# 服务端执行 KILL QUERY 之后
# 客户端自己的输出信息
^C -- query aborted

-- 服务端返回给客户端的信息
ERROR 1317 (70100): Query execution was interrupted

从上面的输出中我们可以看到,客户端Ctrl+C其实是向服务器发送了KILL QUERY命令。

这个和我们手动执行KILL QUERY命令是一样的,接下来我们看一下服务器是如何执行KILL QUERY命令的。

2. 终止查询

在KILL QUERY命令之前,客户端已经发出了一条SQL语句,服务器也分配了一个线程专门用来执行该SQL语句。

在执行SQL之前,客户端按Ctrl+C发出KILL QUERY命令,服务端收到命令后,调度另外一个线程去执行KILL QUERY命令。

为了介绍方便,我们把执行SQL的线程称为一个线程,把执行KILL QUERY命令的线程称为一个Kill线程。

注意:MySQL 内部没有做出这种区分。

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

步骤1:Kill 根据query id查找线程,若没有找到则KILL QUERY命令结束;若找到则转步骤2。

query id是show执行结果里的id字段。

步骤2:Kill 检查当前连接的MySQL用户是否有权限杀死该线程,如果没有权限,则KILL QUERY命令结束;如果有权限,则转至步骤3。

步骤3:确定线程是否正在读取或写入数据字典表。

若否,则Kill线程继续执行第4~6步;若是,则Kill线程的使命到此结束,接力棒交给该线程。

当线程完成对数据字典表的读写后,会立即开始执行KILL QUERY命令的第3步到第6步。

这样的话,步骤3就会被执行两次(一次用于Kill线程,一次用于Kill线程)。

步骤4,设置线程的属性,此时线程处于标记为即将被杀死,但还未被杀死的状态。

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

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

第六步,判断线程是否持有条件变量(存储在)。

如果被持有,则会向等待该条件变量的其他线程发送广播通知,告诉它们可以继续执行。

通过前面的介绍我们可以看出:

不管是你杀死线程,还是线程自己执行第3步到第6步,都只是标记该线程,而不是直接杀死它。

线程是如何被杀死的?请继续阅读。

3. 自杀

为什么不直接在 KILL QUERY 执行期间终止线程?

不是我不想,而是我不能。

因为无论线程执行什么操作,它都需要执行收尾工作,保证它有始有终。

如果直接杀死线程,就没有来得及完成收尾工作,比如已经申请的内存得不到释放,会造成内存泄漏。

因此,如果想要正常地杀死一个线程,需要被杀死的线程主动配合Kill线程。

正确终止线程的场景是这样的:

杀死线程对线程说:我要杀了你。

主题答案:不用了,我自己来。

MySQL 通过在代码的各个角落嵌入点来实现这一场景。嵌入逻辑如下:

判断当前线程是否被标记,若是,则中断当前操作,进入完成阶段。

例如:

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

从上面的代码我们可以看出,在操作的执行过程中,如果发现读取错误(对应本文中的场景,线程被标记),则直接打破循环,中断执行。

4. 回滚

在线程执行过程中,事务可能增加、删除或者修改了某些数据,中断正在进行的操作后,需要将事务回滚。

当线程的执行流返回到d()时:

int mysql_execute_command(THD *thd, bool first_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->()返回true,表示事务执行过程中出现错误,对应本文的场景,事务被KILL QUERY中断,会执行(thd)回滚事务。

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

至此,KILL QUERY的基本介绍已经完成。

之所以说基本介绍完成了,是因为还剩下一点点。

前面我们介绍过,当一个线程执行到某个跟踪点时,如果确定自己已经被标记为即将被杀死,那么它就会中断执行。

但也有小概率,线程在执行过程中通过了所有的嵌入点之后才会被标记为被杀死,线程将没有机会中断执行。

此时就会进入上述代码中的else分支,执行(thd),并提交事务。

考虑到进入else分支提交事务的可能性非常小,我们可以假设,只要客户端按下Ctrl+C,线程就会中断执行并回滚事务。

5. 结论

客户端连接上MySQL之后,会向服务器发送一条SQL语句,在执行这条SQL语句之前,如果客户端按了Ctrl+C,那么其实会向服务器发送一个KILL QUERY命令,和手动执行kill query效果一样。

服务器会分配一个空闲线程(Kill )专门用于执行kill query操作,并标记该线程。

如果要被杀死的线程()正在读写数据字典表,那么它会从被杀死的线程手中接过接力棒,并对自己进行标记。

当线程发现自己被标记时,它会中断执行并在 d() 方法中回滚事务。

需要说明一点,上一节只是以SQL为例介绍了KILL QUERY,其他SQL语句的KILL QUERY流程是一样的。

6. 额外

前面的1到5节描述了没有通过begin语句明确启动事务,并且系统变量的值为ON的情况。

如果你通过begin明确开启事务,或者设置系统变量的值为OFF,那么前面1到5节的内容也适用,但是会稍有差别:

4.回滚段只能作用于事务中的一个SQL,不会影响整个事务,整个事务是否提交或者回滚取决于我们是否向服务器发送了语句。

1、

2、

3.

4.

5.

二维码
扫一扫在手机端查看

本文链接:https://by928.com/4928.html     转载请注明出处和本文链接!请遵守 《网站协议》
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。

项目经理在线

我们已经准备好了,你呢?

2020我们与您携手共赢,为您的企业形象保驾护航!

在线客服
联系方式

热线电话

13761152229

上班时间

周一到周五

公司电话

二维码
微信
线