学习总结录 学习总结录
首页
归档
分类
标签
  • 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深入-查询一行数据执行慢
      • 查询长时间不返回
        • 等 MDL 锁
        • 等flush
        • 等行锁
      • 查询慢
      • 参考
    • 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深入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深入16-查询一行数据执行慢

# MySQL深入-查询一行数据执行慢

一些复杂语句我们会对其进行优化,但是有时候即使查询一行数据也会变得非常慢。

通过下面的语句:给一张简单表插入10w条数据

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();

# 查询长时间不返回

# 等 MDL 锁

select * from t where id=1;

一般碰到这种情况的话,大概率就是表t被锁住了。我们来复现一下这个情况,然后再进行分析。

image-20220617104013793

session A 通过 lock table 命令持有表 t 的 MDL 写锁,而 session B 的查询需要获取 MDL 读锁。所以,session B 进入等待状态。

我们可以执行show processlist命令,来查看当前语句处于什么状态

image-20220617104300473

可以看到ID=70这条SQL语句state是Waiting for table metadata lock,出现这个状态表示:现在有一个线程正在表t上请求或者持有MDL写锁,把select语句堵住了。

这类问题的处理方式:就是找到谁持有MDL写锁,然后把它kill掉。

通过查询 sys.schema_table_lock_waits 这张表,我们就可以直接找出造成阻塞的 process id,把这个连接用 kill 命令断开即可。

# 等flush

首先还是来复现一下情况:

image-20220617105201234

session A中相当于每一行休眠1秒,t中一共有10w行,所以要默认执行10万秒。

image-20220617105751551

可以查看ID=77的SQL语句线程状态是waiting for table flush,这个状态表示:现在有一个线程要对表t做flush操作,一般有以下两个:

  • flush tables t with read lock;
  • flush tables with read lock;

如果指定表 t 的话,代表的是只关闭表 t;如果没有指定具体的表名,则表示关闭 MySQL 里所有打开的表。

id=78的SQL阻塞原因:有一个 flush tables 命令被别的语句堵住了,然后它又堵住了我们的 select 语句。

在这期间表 t 一直是被 session A“打开”着。然后,session B 的 flush tables t 命令再要去关闭表 t,就需要等 session A 的查询结束。这样,session C 要再次查询的话,就会被 flush 命令堵住了。

# 等行锁

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

我们知道这条语句访问id=1这条记录会加读锁,如果这个时候已经有一个事务在这行记录上持有一个写锁,我们的select语句就会被堵住。

image-20220617134815812

session A启动了事务,占据了id=1这条记录的写锁,然后也没有提交,导致session B被堵住。

可以通过以下命令查看到谁占着这个写锁:

select * from sys.innodb_lock_waits where locked_table='`test`.`t`'

image-20220617135751659

image-20220617135805462

这里通过kill 80,直接断开这个连接。断开连接的时候,会自动回滚这个连接里面正在执行的线程·,也就释放了id=1上的行锁。

# 查询慢

select * from h where c=50000 limit 1;

由于字段c上没有索引,所以这条语句只能一条一条遍历,因此需要扫描5W行。

# Time: 2022-06-17T06:15:31.105116Z
# User@Host: root[root] @ localhost [::1]  Id:    84
# Query_time: 0.006709  Lock_time: 0.000189 Rows_sent: 1  Rows_examined: 50000
SET timestamp=1655446531;
select * from h where c=50000 limit 1;

下面看一个只扫描一行的查询语句:

select * from t where id=1;
# Time: 2022-06-17T06:21:40.038710Z
# User@Host: root[root] @ localhost [::1]  Id:    84
# Query_time: 0.000141  Lock_time: 0.000065 Rows_sent: 1  Rows_examined: 1
SET timestamp=1655446900;
select * from h where id=1;

下面我们试想一个场景,如果事务A在查询之前,事务B让某一行数据增加了100W次,

那么在事务A拿到数据的时候是最新的数据,但是这个数据不属于它,就会使用undo log往前面找,把属于自己的结果返回。

image-20220617143236987

session B 更新完 100 万次,生成了 100 万个回滚日志 (undo log)。

带 lock in share mode 的 SQL 语句,是当前读,因此会直接读到 1000001 这个结果,所以速度很快;而 select * from t where id=1 这个语句,是一致性读,因此需要从 1000001 开始,依次执行 undo log,执行了 100 万次以后,才将 1 这个结果返回。

image-20220617143549612

# 参考

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

#MySQL
上次更新: 2024/06/29, 15:13:44
MySQL深入15-索引失效案例分析
MySQL深入17-幻读

← MySQL深入15-索引失效案例分析 MySQL深入17-幻读→

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