欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MySQL_查询一条SQL慢的原因

程序员文章站 2022-05-09 20:53:56
...

查询一条SQL慢的原因

学习总结

  1. 等待MDL锁,5.7版本之后MDL过程

    • 获取MDL写锁
    • 降级为MDL读锁
    • 真正MDL操作
    • 升级为写锁
    • 释放锁
  2. 等待flush操作

    flush tables t with read lock; 只锁定表t
    lush tables with read lock; 关闭mysql所有表
    
  3. 等行锁 如果一个行记录有行锁就会被锁住

    • 写锁,只有本线程可以读、写,其他线程读写都不行 X排它锁
    • 读锁,本线程可以读,其他线程可以读,不能写 S共享锁
    • 查看行锁情况
    mysql> select * from t sys.innodb_lock_waits where locked_table=`'test'.'t'`\G
    直接kil blocking_pid 就可以
    
    MySQL_查询一条SQL慢的原因
  4. lock in share mod 和 select … for update 是当前读

  5. 最后的问题很精点,仔细看一下

未提交事务导致查询慢

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

查询语句

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

由于字段 c 上没有索引,这个语句只能走 id 主键顺序扫描,因此需要扫描 5 万行。

作为确认,你可以看一下慢查询日志。注意,这里为了把所有语句记录到 slow log 里,我在连接后先执行了 set long_query_time=0,将慢查询日志的时间阈值设置为 0。

MySQL_查询一条SQL慢的原因

Rows_examined 显示扫描了 50000 行。你可能会说,不是很慢呀,11.5 毫秒就返回了,我们线上一般都配置超过 1 秒才算慢查询。但你要记住:坏查询不一定是慢查询。我们这个例子里面只有 10 万行记录,数据量大起来的话,执行时间就线性涨上去了。

扫描行数多,所以执行慢,这个很好理解。

但是接下来,我们再看一个只扫描一行,但是执行很慢的语句。

如图 所示,是这个例子的 slow log。可以看到,执行的语句是

mysql> select * from t where id=1;

虽然扫描行数是 1,但执行时间却长达 800 毫秒。

MySQL_查询一条SQL慢的原因

看上去是不是更奇怪了?按理说 lock in share mode 还要加锁,时间应该更长才对啊。

图 是这两个语句的执行输出结果。

MySQL_查询一条SQL慢的原因

第一个语句的查询结果里 c=1,带 lock in share mode 的语句返回的是 c=1000001。看到这里应该有更多的同学知道原因了。如果你还是没有头绪的话,也别着急。我先跟你说明一下复现步骤,再分析原因。

MySQL_查询一条SQL慢的原因

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

MySQL_查询一条SQL慢的原因

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

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

注意,undo log 里记录的其实是“把 2 改成 1”,“把 3 改成 2”这样的操作逻辑,画成减 1 的目的是方便你看图。

查询结果为空,为什么返回这么慢

表结构

mysql> CREATE TABLE `table_a` (
  `id` int(11) NOT NULL,
  `b` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `b` (`b`)
) ENGINE=InnoDB;

假设现在表里面,有 100 万行数据,其中有 10 万行数据的 b 的值是’1234567890’, 假设现在执行语句是这么写的:

mysql> select * from table_a where b='1234567890abcd';

最理想的情况是,MySQL 看到字段 b 定义的是 varchar(10),那肯定返回空呀。

可惜,MySQL 并没有这么做。那要不,就是把’1234567890abcd’拿到索引里面去做匹配,肯定也没能够快速判断出索引树 b 上并没有这个值,也很快就能返回空结果。

但实际上,MySQL 也不是这么做的。这条 SQL 语句的执行很慢,

流程是这样的:

  1. 在传给引擎执行的时候,做了字符截断。因为引擎里面这个行只定义了长度是 10,所以只截了前 10 个字节,就是’1234567890’进去做匹配;
  2. 这样满足条件的数据有 10 万行;
  3. 因为是 select *, 所以要做 10 万次回表;
  4. 但是每次回表以后查出整行,到 server 层一判断,b 的值都不是’1234567890abcd’;
  5. 返回结果是空。
相关标签: MYSQL骑马的路