《MySQL实战45讲》——学习笔记19 “SQL查一行执行慢的排查、锁等待/一致性读“【建议收藏】

news2024/12/27 18:10:13

由于SQL本身的写法问题(如join太多表、未走索引/索引失效、一次查太多数据等),或是MySQL节点CPU占用率很高或IO利用率很高,都会导致一条SQL执行的比较慢;但是有时候,"只查一行数据",也会出现"比较慢"的现象;

本篇例举在一个在一个简单的表上执行"查一行“的SQL语句可能出现被锁住和执行慢现象的例子,其中涉及到了表锁、行锁和一致性读的概念;

为了便于描述,下面构造一个表,基于这个表来说明问题;这个表有两个字段id和c,并且我在里面插入了(1,1)、(2,2)...、(100000,100000)这10万行记录;

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

现象A:查单条记录的SQL执行后长时间不返回

现象描述:执行下面的一条简单的SQL语句,查询结果长时间不返回;

select * from t where id=1;

一般碰到这种情况的话,大概率是表t被锁住了;分析原因的时候,一般都是首先执行一下show processlist命令,看看当前语句处于什么状态,然后再针对每种状态,去分析它们产生的原因、如何复现,以及如何处理;

情况1:等MDL锁

元数据锁(metadata lock)简称MDL锁,是表级锁;MDL不需要显式使用,在访问一个表的时候会被自动加上,它的作用是保证读写的正确性;

当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁;读锁之间不互斥,读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性;

下面给出这条SQL长时间不返回的简单的复现步骤;

分析:sessionA通过lock table命令持有表t的MDL写锁,而sessionB的查询需要获取MDL读锁;所以,sessionB进入等待状态;

这类问题的处理方式,就是找到谁持有MDL写锁,然后把它kill掉;如何找到这条进程ID呢?——通过查询 sys.schema_table_lock_waits 这张表,就可以直接找出造成阻塞的 process id,把这个连接用 kill 命令断开即可;

情况2:等flush表

接下来,介绍外一种查询被堵住的情况;在表t上,执行下面的SQL语句查看进程列表;

select * from information_schema.processlist;

查出来这个线程的状态是 Waiting for table flush

先来说一下flush表的作用,flush tables意味着关闭所有已打开的表对象,同时将查询缓存中的结果清空;就是说flush tables的一个效果就是会等待所有正在运行的SQL请求结束;因为,SQL语句在执行前,都会打开相应的表对象,如 select * fromt t 语句,会找到表t的frm文件,并打开表内存对象;为了控制表对象使用的内存空间和其他资源,MySQL会隐式(后台表对象管理线程)或显式(flush tables等)来关闭已打开但并没有使用的表对象;然而,正在使用的表对象是不能关闭的(如SQL请求仍在运行),因此,flush tables操作会被正在运行的SQL请求阻塞

正常情况下,flush tables 语句执行起来很快,除非它也被别的线程堵住了;所以,出现Waiting for table flush状态的可能情况是:有一个flush tables命令被别的语句堵住了,然后它又堵住了我们的select语句

复现一下这种情况,复现步骤下;

分析:

(1)在sessionA中,我故意每行都调用一次sleep(1),这样在这个有10W条数据的表中,这个语句默认要执行10万秒,在这期间表t一直是被sessionA“打开”着;
(2)然后,sessionB的flush tablest命令再要去关闭表t,就需要等sessionA的查询结束;
(3)这样,sessionC要再次查询的话,就会被flush命令堵住了;

下图是这个复现步骤的 show processlist 结果;

这个例子的排查也很简单,当看到这个 show processlist 的结果,去找sessionA的线程ID,就要先将sessionA的进程先kill掉,然后等flush tables的命令执行完,select * from t where id=1 的命令就能执行了;

情况3:等行锁

现在,这条语句经过了表级锁的考验,终于来到存储引擎这一层了;执行下面的SQL语句:

select * from t where id=1 lock in share mode;

这条语句的用法表示"当前读",也就是说需要获取最新的数据,因此需要加读锁(S锁,共享锁);如果此时有事务正在更新这条数据,那么这个事务持有这行数据的一个写锁(X锁,排他锁),那这条"当前读"的SQL就需要等这个事务提交后释放锁,释放前, select 语句就会被一直阻塞等待获取锁;

复现一下这种情况,复现步骤下;

分析:sessionA启动了事务,占有写锁,还未提交,导致sessionB被堵住;

这个问题并不难分析,但问题是怎么查出是谁占着这个写锁;如果你用的是MySQL5.7版本,可以通过sys.innodb_lock_waits表查到;查询方法是:

mysql> select * from t sys.innodb_lock_waits where locked_table='`test`.`t`'\G

结果如下:

可以看到,这个信息很全,线程ID=4的S锁阻塞了后面SQL语句,通过 KILL 4 命令即可释放行锁;连接被断开的时候,会自动回滚这个连接里面正在执行的线程,也就释放了 id=1 这条记录上的行锁

现象B:查单条记录的SQL执行实际耗时长

经过了重重封“锁”,再来看看一些查询实际执行慢的例子;

情况1:全表扫描

执行以下SQL语句:

mysql> select * from t where c=50000 limit 1;

由于字段c上没有索引,这个语句只能走id主键顺序扫描,查完所有记录都不满足条件,因此需要扫描10万行;作为确认,可以看一下慢查询日志;

Rows_examined显示扫描了50000行,执行时长11ms;你可能会说,11.5ms就返回了不是很慢,线上一般都配置超过1000ms才算慢查询;但你要记住:坏查询不一定是慢查询,当某个条件变化时如表数据量增大,原先的坏查询就"原形毕露"了;我们这个例子里面只有10万行记录,如果数据量大起来的话,执行时间就线性涨上去了;扫描行数多,所以执行慢,这个很好理解;

情况2:大量更新产生大量的undo log

执行以下SQL语句并查看它的慢日志 slow log:

select * from t where id=1;

现象:虽然扫描行数是1,但执行时间却长达800ms;

再看下一条SQL语句以及他的慢日志:

select * from t where id=1 lock in share mode;

现象:执行时扫描行数也是1行,执行时间是0.2毫秒;使用了 lock in share mode 加读锁,但是实际执行时间却远小于不加锁的查询语句

查看两条语句的查询结果,如下,第一个语句的查询结果里 c=1,带 lock in share mode 的语句返回的是 c=1000001;

分析:说明"当前读"读到的最新版本的数据c的值是1000001,而第一条语句"一致性视图"中这行记录的版本数据c=1,说明这中间一定存在对这条记录的更新操作

复现一下这种情况,复现步骤下;

sessionA先用start transaction with consistent snapshot命令启动了一个事务,之后sessionB执行100万次update语句,此时id=1这一行的状态如下;

sessionB更新完100万次,生成了100万个回滚日志(undolog);带lock in share mode的SQL语句是当前读,因此会直接读到1000001这个结果所以速度很快;而select * from t whereid=1这个语句是一致性读,因此需要从1000001开始,依次执行undolog,执行了100万次以后,才将1这个结果返回,所以较慢

小结

本文介绍了在一个简单的表上执行“查一行”的SQL,也可能会出现执行慢的情况;

(1)对于长时间不返回的情况,原因可能是:等MDL表锁、等flush表(flush之前有较多的还在执行的SQL)、"当前读"等行锁;

(2)对于一个简单查询执行时间远大于预期时间的情况,原因可能是:查询时,未提交的事务中对该行记录执行了大量的更新,导致"一致性读"时需要从当前版本执行多次undolog;

下篇文章:待定

本章参考:19 | 为什么我只查一行的语句,也执行这么慢?-极客时间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/61167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内存优化之重新认识内存

我们知道,手机的内存是有限的,如果应用内存占用过大,轻则引起卡顿,重则导致应用崩溃或被系统强制杀掉,更严重的情况下会影响应用的留存率。因此,内存优化是性能优化中非常重要的一部分。但是,很…

深入体会线程状态的切换

✨✨hello,愿意点进来的小伙伴们,你们好呐! 🐻🐻系列专栏:【JavaEE初阶】 🐲🐲本篇内容:线程状态详解 🐯🐯作者简介:一名现大二的三非编程小白&am…

微机-------CPU与外设之间的数据传送方式

目录 一、无条件方式二、查询方式三、中断方式四、DMA方式一、无条件方式 外设要求:简单、数据变化缓慢。 外设被认为始终处于就绪状态。始终准备好数据或者始终准备好接收数据。 IN AL,数据端口 数据端口的地址通过CPU的地址总线送到地址译码器进行译码,同时该指令进行的是…

JAVASE(复习)——异常

所有的异常都是在java.lang包中的Throwable类中 一、Exception 和 Error 的区别 exception:程序本身发生的异常,可以捕获抛出异常,一般用try—catch—finally捕获。 error:发生在jvm层面的错误,程序无法处理。 二…

Git 如何调整 commit 的顺序

title: Git 如何调整 commit 的顺序 date: 2022-12-02 23:11 tags: [git] 〇、问题 使用哪条命令调整commit的顺序? git rebase -i 一、前言 今天测试了git hooks,产生了大量的commit,而后又进行了正常的commit,因此在这里是想要…

java——mybatis——Mybatis注解开发——@Update——修改数据

DAO接口: package com.sunxl.dao;import com.sunxl.pojo.User; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.SelectKey; import org.apache.ibatis.annotations.Update;impo…

SpringBoot+Thymeleaf上传头像并回显【表单提交】

参考文章:springbootthymeleaf实现图片上传并回显https://www.wanmait.com/note/shaowei/javaee/b3717a24fde24d3e89c47765a1a63214.html 一、新建SpringBoot项目 添加 spring web和 thymeleaf 的依赖 二、在templates新建页面 在页面中添加一个表单和一个文件上传…

8086,8088CPU管脚,奇偶地址体, ready信号,reset复位信号。规则字和非规则字

8086/8088均为40条引线,双列直插式封装,某些引线有多重功能,其功能转换有两种情况:一种是分时复用,一种是按组态定义。 用8088微处理器构成系统时,有两种不同的组态: 最小组态:808…

@AutoWired与@Resource

参考 : Qualifier - 搜索结果 - 知乎 Autowired和Resource的区别是什么? - 知乎 面试突击78:Autowired 和 Resource 有什么区别? - 掘金 目录 同一类型多个Bean报错问题 Resource注解 Resource的查找顺序 Resource注解实现依赖注入 Reso…

网课题库接口调用方法

网课题库接口调用方法 本平台优点: 多题库查题、独立后台、响应速度快、全网平台可查、功能最全! 1.想要给自己的公众号获得查题接口,只需要两步! 2.题库: 查题校园题库:查题校园题库后台(点…

QT对象树机制

Qt提供了对象树机制,能够自动、有效的组织和管理继承自QObject的Qt对象。 每个继承自QObject类的对象通过它的对象链表(QObjectList)来管理子类对象,当用户创建一个子对象时,其对象链表相应更新子类对象信息&#xff0…

Docker快速入门

容器Docker技术的演进 1.曾经部署应用,使用物理机部署,这可能会因为不同应用所依赖的版本号不同,不得已购买一套全新的机器,所以成本高、部署慢、资源浪费、难以迁移和拓展、可能会被限定硬件厂商。 2.之后引入了VMVare&#xff…

使用JPA和Hibernate查询分页

介绍 受到我最近给出的StackOverflow答案的启发,我决定是时候写一篇关于使用JPA和Hibernate时查询分页的文章了。 在本文中,您将了解如何使用查询分页来限制 JDBC大小并避免获取不必要的数据。ResultSet 如何在#Hibernate中使用查询分页来限制 JDBC 结…

pytorch深度学习实战lesson32

第三十二课 分布式训练 这个是15年的时候沐神在 CMU 装的一个小机群,里面有30台机器,各机群有大概60块 GPU , 60块 GPU一共花了三四万美金的样子,就是大概20万人民币。沐神表示最亏的是当年他们跑了太多深度学习的实验&#xff0c…

C语言-const char*,char const*,char *const理解

By: Ailson Jack Date: 2022.12.04 个人博客:http://www.only2fire.com/ 本文在我博客的地址是:http://www.only2fire.com/archives/150.html,排版更好,便于学习,也可以去我博客逛逛,兴许有你想要的内容呢。…

传奇外网开服教程-GEE传奇外网全套架设教程

版本不同,所用的引擎和配置也会不同,但是架设方法都是大同小异,今天明杰给大家分享GEE引擎的外网架设教程。​ 需要准备的东西:DBC200版本,补丁,客户端,服务器,备案域名&#xff0c…

【Typora】Typora 新手入门参数配置记录

目录 写在前面 更改图片大小 更换高亮背景 更换主题 写在前面 最近发现一款记笔记的软件——Typora,极简清爽的外观一下子就把我给吸引住了,它支持Markdown 的格式记录,可以让笔记更加有条理、美观,至于 typora 的一些写作语法…

Android入门第43天-Activity与Activity间的互相传值

介绍 今天的课程会比较好玩,我们在之前的Service篇章中看到了一种putExtras和getExtras来进行activity与service间的传值。而恰恰这种传值其实也是Android里的通用传值法。它同样可以适用在activity与activity间传值。 Android中的传值 传单个值 传多个值 具体我…

Spring注解(简便地使用 Bean )

目录 0. 前置工作 1. 将 Bean 存储到容器 2. 对象注入&#xff08;对象装配&#xff09;【从容器中将对象读取出来】 0. 前置工作 创建Maven项目后&#xff0c;在pom.xml中添加Spring所必须的依赖。 <dependencies><dependency><groupId>org.springframe…

22个每个程序员都应该知道的 Git 命令

在这篇文章中&#xff0c;我写了一个快速学习 git 命令的备忘单。它将包括开发人员每天使用的命令&#xff0c;如 git add、git commit、git pull、git fetch&#xff0c;并共享其他有用的 git 命令。 我一直使用Git的一些命令&#xff0c;今天这个列表清单&#xff0c;希望也…