笔记记录自林晓斌(丁奇)老师的《MySQL实战45讲》

(本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除)

19) --为什么我只查一行的语句,也执行这么慢?

  需要说明一下,如果MySQL数据库本身就有很大的压力,导致数据库服务器CPU占用率很高或ioutil(IO利用率)很高,这种情况下所有的语句执行都有可能变慢,不属于我们今天的讨论范围。为了便于描述,我们构造一个表,基于这个表来说明问题。这个表有两个字段id和c,并且我们事先插入了10万行记录。

mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB; delimiter ;;
create procedure idata()
begin
declare i int;
set i=1;
while(i<=100000)do
insert into t values(i,i);
set i=i+1;
end while;
end;;
delimiter ; call idata();

第一类:查询长时间不返回

  执行如下语句 select * from t where id = 1;结果很长时间不返回结果,这是为什么呢?一般这种情况,大概率是表t被锁住了。这种时候我们就需要show processlist命令来查看当前语句处于什么状态了。然后根据每种状态,分析原因,如何复现以及如何处理。

等MDL锁:

  当我们使用show processlist命令时如果看到了 Waiting for table metadata lock的结果,就表示有一个线程正在表t上请求或者持有MDL写锁,把select语句堵住了。我们可以这么来复现:

 Session A通过lock table命令持有表t的MDL写锁,而session B的查询需要获取MDL读锁。所以,session B进入等待状态。而这类问题的处理方式,就是找到持有MDL写锁的家伙,然后把它kill掉。但是,由于在show processlist的结果里面,session A的Command列是“Sleep”,导致查找起来不方便。不过有了performance_schema和sys系统库以后,就方便多了。(MySQL启动时需要设置performance_schema=on,相比于设置为off,会有10%的性能损失)。通过查询sys.schem_table_lock_waits这张表,我们就可以直接找出造成阻塞的process id,把这个连接用kill命令断开即可。

等flush

  如果我们的show processlist看到了"Waiting for table flush",说明现在有一个线程正要对表t做flush操作。MySQL里面对表做flush操作的用法,一般有下面两个:

flush tables t with read lock;

flush tables with read lock;

  如果指定了表t的话,代表只关闭表t,如果不指定具体的表名,则表示关闭MySQL里的所有打开的表。但正常情况下,这两个语句执行起来都很快,触发它们也被别的线程堵住了。

  所以,出现Waiting for table flush状态的可能情况是:有一个flush tables命令被别的语句堵住了,然后它又堵住了我们的select语句。你可以按下面的方式复现:

  这样在session A中每行都会调用sleep(1),这样表t一直是被session"打开",然后,session B的flush tables命令要去关闭表t,就需要等待session A的查询结束。这样,session C要再次查询的话,就会被flush命令堵住了。这个例子的处理方式很前面的一样,就不再赘述了。

等行锁:

  经过上面的考验,我们的select语句终于来到了引擎里

mysql> select *from t where id - 1 lock in share model;

  由于访问id=1这个记录时要加读锁,如果这个时候已经有一个事务在这行记录上持有一个写锁,我们的select语句就会被堵住。复现步骤如下:

  这时如果你使用show processlist命令会发现statistics。我们来看复现步骤,sessionA启动了事务,占用写锁,还不提交,是导致sessionB被堵住的原因。这个问题不难分析,但问题是怎么查出是谁占用了这个写锁,如果你的MySQL5.7或以上版本,你可以通过sys.innodb_lock_waits表查到,查询命令是:

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

  但这里有一个需要注意的地方,当你查出罪魁祸首,比如线程4,你不能直接使用kill query 4;这个命令表示停止4号线程当前正在执行的语句,而这个方法其实是没有用的。因为占有行锁的是update语句,这个语句已经是之前执行完成了的,现在执行kill query,无法让这个事务去掉id=1的行锁。实际上,kill 4 才有效。也就是说直接断开这个连接。这里隐含的一个逻辑是,连接被断开的时候,会自动回滚这个连接里面正在执行的线程,也就释放了id=1的行锁。

第二类:查询慢

  我们先来看一条sql语句

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

  由于字段c上没有索引,这个语句只能走id主键顺序扫描,因此需要扫描50000行。你可以先在慢查询日志里确认一下。为了确保被慢查询日志记录下来,你可以先执行set long_query_time=0。将慢查询日志的时间阈值设置为0.虽然实际上你可能会看到返回结果不是很慢,但有一个原则是这样:坏查询不一定是慢查询。我们这个例子里面只有10万行记录,数据量大起来的话,执行时间就会线性涨上去了。

  扫描行数多,执行慢,这个很好理解。我们再来看一个只扫描了一行,但是执行很慢的语句。执行的语句是这样的。

mysql>select * from t where id=1;

 它的查询日志是这样的:

  还是这个查询,如果我们再加上 lock in share model,执行时间居然会变快:

  这是为什么呢?按理来说,加上了锁不应该更慢吗?我们再来看一个提示信息,下图是这两个语句的执行输出结果:

我们的第一个查询返回的结果是c=1,而带lock in share mode的语句返回的是c=1000001。如果你还是没有头绪,也别着急,我们来看看复现步骤:

你看到了,session A先用start transaction with consistent snapshot命令启动了一个事务,之后sessionB才开始执行update语句。SessionB执行完100万次update语句后,id=1这一行处于什么状态呢?你可以从下图找到答案:

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

  注意,undo log里面的记录其实是“把2改成1”这样,而不是图中的“-1”;

MySQL 笔记整理(19) --为什么我只查一行的语句,也执行这么慢?的更多相关文章

  1. 最全mysql笔记整理

    mysql笔记整理 作者:python技术人 博客:https://www.cnblogs.com/lpdeboke Windows服务 -- 启动MySQL net start mysql -- 创 ...

  2. 《Mysql - 为什么只查一行的数据,也这么慢?》

    概念 - 在某些情况下,“查一行”,也会执行得特别慢. - 下面分析在什么情况下,会出现这个现象. - 基础工作(构建数据库环境) - 建立 t 表,并写入 10W 的数据. CREATE TABLE ...

  3. MySQL 笔记整理(16) --“order by”是怎么工作的?

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 16) --“order by”是怎么工作的? 在林老师的课程中,第15 ...

  4. MySQL 笔记整理(5) --深入浅出索引(下)

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 5) --深入浅出索引(下) 这次的笔记从一个简单的查询开始: 建表语句是这样的 mysql> create table T ...

  5. MySQL 笔记整理(17) --如何正确地显示随机消息?

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 17) --如何正确地显示随机消息? 如果有这么一个英语单词表,需要每次 ...

  6. MySQL 笔记整理(9) --普通索引和唯一索引,应该怎么选择?

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 9) --普通索引和唯一索引,应该怎么选择? 假如你在维护一个市民系统, ...

  7. MySQL 笔记整理(1) --基础架构,一条SQL查询语句如何执行

    最近在学习林晓斌(丁奇)老师的<MySQL实战45讲>,受益匪浅,做一些笔记整理一下,帮助学习.如果有小伙伴感兴趣的话推荐原版课程,很不错. 1) --基础架构,一条SQL查询语句如何执行 ...

  8. MySQL 笔记整理(20) --幻读是什么,幻读有什么问题?

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 20) --幻读是什么,幻读有什么问题? 我们先来看看表结构和初始化数据 ...

  9. MySQL 笔记整理(2) --日志系统,一条SQL查询语句如何执行

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 2) --日志系统,一条SQL查询语句如何执行 MySQL可以恢复到半个月内任意一秒的状态,它的实现和日志系统有关.上一篇中记录了一 ...

随机推荐

  1. 记录遭遇挖矿程序kthrotlds的失败处理经历

    1 发现问题 在腾讯云上购买了一个centos7的服务器,平时用来练手,偶尔也安装一些程序进行测试,上面安装了mysql和redis,前段时间数据库经常掉线,连不上,到腾讯云后台进行查看,通过服务器实 ...

  2. k8s日志收集方案

    k8s日志收集方案 三种收集方案的优缺点: 下面我们就实践第二种日志收集方案: 一.安装ELK 下面直接采用yum的方式安装ELK(源码包安装参考:https://www.cnblogs.com/De ...

  3. Docker入门学习

    Python爬虫 最近断断续续的写了几篇Python的学习心得,由于有开发经验的同学来说上手还是比较容易,而且Python提供了强大的第三方库,做一个小的示例程序还是比较简单,这不我之前就是针对Pyt ...

  4. spring boot sharding-jdbc实现分佈式读写分离和分库分表的实现

    分布式读写分离和分库分表采用sharding-jdbc实现. sharding-jdbc是当当网推出的一款读写分离实现插件,其他的还有mycat,或者纯粹的Aop代码控制实现. 接下面用spring ...

  5. 微服务(入门二):netcore通过consul注册服务

    基础准备 1.创建asp.net core Web 应用程序选择Api 2.appsettings.json 配置consul服务器地址,以及本机ip和端口号信息 { "Logging&qu ...

  6. CopyOnWriteArrayList你都不知道,怎么拿offer?

    前言 只有光头才能变强 前一阵子写过一篇COW(Copy On Write)文章,结果阅读量很低啊...COW奶牛!Copy On Write机制了解一下 可能大家对这个技术比较陌生吧,但这项技术是挺 ...

  7. 并发系列(7)之 ScheduledThreadPoolExecutor 详解

    文本将主要讲述 ThreadPoolExecutor 一个特殊的子类 ScheduledThreadPoolExecutor,主要用于执行周期性任务:所以在看本文之前最好先了解一下 ThreadPoo ...

  8. C#编写扫雷游戏

    翻看了下以前大学学习的一些小项目,突然发现有个项目比较有意思,觉得有必要把它分享出来.当然现在看来,里面有很多的不足之处,但因博主现在已经工作,没有时间再去优化.这个项目就是利用C#编写一个Windo ...

  9. MongoDB之基本操作与日常维护

    MongoDB基本操作 MongoDB的基本操作主要是对数据库.集合.文档的操作,包括创建数据库.删除数据库.插入文档.更改文档.删除文档.和查询文档. 操作 描述 show dbs 查看当前实例下的 ...

  10. 设计模式之桥接模式——Java语言描述

    桥接适用于把抽象化和实现化解耦,使得二者可以独立变化.这种类型的设计模式属于结构性模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦 这种模式设计到一个作为桥接的接口,使得实体类的功能独立 ...