学习总结录 学习总结录
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
  • Java基础

  • Java集合

  • MySQL

    • MySQL深入01-查询语句
    • MySQL深入02-更新语句
    • MySQL深入03-事务隔离
    • MySQL深入04-深入浅出索引
    • MySQL深入05-全局锁、表锁、行锁
    • MySQL深入06-事务隔离再探
    • MySQL深入07-普通索引和唯一索引
    • MySQL深入08-为什么会选错索引
    • MySQL深入09-字符串字段加索引
    • MySQL深入10-脏页刷新
    • MySQL深入11-数据库表空间回收
    • MySQL深入12-count()
    • MySQL深入13-order by
    • MySQL深入14-正确显示随机消息
    • MySQL深入15-索引失效案例分析
    • MySQL深入16-查询一行数据执行慢
    • MySQL深入17-幻读
    • MySQL深入18-改一行语句锁问题
    • MySQL深入19-暂时提高数据库性能方案
    • MySQL深入20-这么保证数据不丢
    • MySQL深入21-主备一致的保证
    • MySQL深入22-高可用性的保证
    • MySQL深入23-备库延迟好几个小时
    • MySQL深入24-主库出问题,从库这么办
    • MySQL深入25-读写分离的过期读问题
    • MySQL深入26-判断数据库是否出问题
    • MySQL深入27-误删数据的处理方案
    • MySQL深入28-kill不掉的语句
      • MySQL深入28-kill不掉的语句
      • 收到kill之后,线程做什么
      • 案例分析
      • 两个关于客户端的误解
      • 参考
    • MySQL深入29-查询对内存的影响
    • MySQL深入30-join深入
    • MySQL深入31-join语句优化
    • MySQL深入32-临时表深入
    • MySQL深入33-内部临时表何时使用
    • MySQL深入34-InnoDB和Memory
    • MySQL深入35-自增主键为什么不连续
    • MySQL深入36-insert语句的锁
    • MySQL深入37-如何快速复制一张表
    • MySQL深入38-grant和flush privileges
    • MySQL深入39-分区表
    • MySQL深入40-自增id用完如何处理
  • Redis

  • JVM

  • 多线程

  • 计算机网络

  • Spring

  • Kafka

  • Elasticsearch

  • Python

  • 面试专题

  • 知识库
  • MySQL
旭日
2023-03-31
目录

MySQL深入28-kill不掉的语句

# MySQL深入28-kill不掉的语句

MySQL中有两个kill命令:

  • kill query + 线程 id 表示终止这个线程中正在执行的语句
  • kill connection + 线程id 表示断开这个线程的连接

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

还有一种情况是,语句处于锁等待的时候,直接使用kill命令:

image-20220630150812161

session C 执行 kill query 以后,session B 几乎同时就提示了语句被中断。

# 收到kill之后,线程做什么

session B被终止掉线程后,并不是什么不都管了直接退出。当对一个表做增删改查操作的时候,会在表上加MDL读锁,所以session B虽然处于blocked状态,但还是拿着一个MDL读锁。如果线程被KILL之后,直接终止,那么MDL读锁就没有机会释放了。

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

有点类似java中的interrupt()通知你该结束了,而不是强制结束。

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

  • 把 session B 的运行状态改成 THD::KILL_QUERY
  • 给 session B 的执行线程发一个信号

为什么要发送信号

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

上面的分析包含了三层含义:

  • 一条语句执行过程中会有多处检测点,在这些检测点判断线程状态,如果发现线程状态是THD::KILL_QUERY,才开始进入语句终止逻辑;
  • 如果处于等待状态,必须是一个可以被唤醒的等待,否则根本不会执行到监测点。
  • 语句从开始进入终止逻辑,到终止逻辑完全完成,是有一个过程的。

下面来分析一个kill不掉的例子,也就是innodb_thread_concurrency 不够用的例子。

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

image-20220630153133670

  1. session C执行的时候被堵住了
  2. session D 执行的 kill query C 命令却没什么效果,
  3. session E 执行了 kill connection 命令,才断开了 session C 的连接,提示“Lost connection to MySQL server during query”,
  4. 如果在 session E 中执行 show processlist,你就能看到下面这个图。

image-20220630153309674

id=12这个线程显示的killed,也就是说断开的是mysql客户端与服务端之间的链接,语句还在执行中。

# 案例分析

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

12号线程的状态虽然已经被设置成了KILL_QUERY,但是在这个等待进入InnoDB过程中,是没有去判断线程的状态的,而一旦不去判断线程的状态,就不会进入终止逻辑阶段。

session E执行kill connection命令时,流程如下:

  • 把 12 号线程状态设置为 KILL_CONNECTION;
  • 关掉 12 号线程的网络连接。因为有这个操作,所以你会看到,这时候 session C 收到了断开连接的提示。

同时由于执行show processlist有一个特别逻辑:如果一个线程的状态是KILL_CONNECTION,就把Command列显示成Killed。所以我们看到12号线程显示为killed。

线程什么时候会退出呢?

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

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

另一类情况:终止逻辑耗时很长。也就是说在终止的过程中使用命令show processlist进行查询也会显示Command=Killed。这种场景有如下几种:

  • 超大事务执行期间被 kill。这时候,回滚操作需要对事务执行期间生成的所有新数据版本做回收操作,耗时很长。
  • 大查询回滚。如果查询过程中生成了比较大的临时文件,加上此时文件系统压力大,删除临时文件可能需要等待 IO 资源,导致耗时较长。
  • DDL 命令执行到最后阶段,如果被 kill,需要删除中间过程的临时文件,也可能受 IO 资源影响耗时较久。

# 两个关于客户端的误解

误解1:如果库里面表特点多,连接就很慢

有些线上的库,会包含很多表,这时候去连接数据库客户端页面会卡住。但是这个所谓的卡住和数据库表的个数无关。

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

但实际上,正如图中的文字提示所说的,当使用默认参数连接的时候,MySQL 客户端会提供一个本地库名和表名补全的功能。为了实现这个功能,客户端在连接成功后,需要多做一些操作:

  • 执行 show databases;
  • 切到 db1 库,执行 show tables;
  • 把这两个命令的结果用于构建一个本地的哈希表。

在这些操作中,最花时间的就是第三步在本地构建哈希表的操作。所以,当一个库中的表个数非常多的时候,这一步就会花比较长的时间。如果在连接命令中加上 -A,就可以关掉这个自动补全的功能,然后客户端就可以快速返回了。

误解2:–quick

设置这个参数并非是让服务端加速,实际上恰恰相反,设置了这个参数可能会降低服务端的性能。

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

  • 一种是本地缓存,也就是在本地开一片内存,先把结果存起来。如果你用 API 开发,对应的就是 mysql_store_result 方法。
  • 另一种是不缓存,读一个处理一个。如果你用 API 开发,对应的就是 mysql_use_result 方法。

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

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

# 参考

MySQL 实战 45 讲-极客时间 (opens new window)

#MySQL
上次更新: 2024/06/29, 15:13:44
MySQL深入27-误删数据的处理方案
MySQL深入29-查询对内存的影响

← MySQL深入27-误删数据的处理方案 MySQL深入29-查询对内存的影响→

最近更新
01
基础概念
10-31
02
Pytorch
10-30
03
Numpy
10-30
更多文章>
Theme by Vdoing | Copyright © 2021-2024 旭日 | 蜀ICP备2021000788号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式