mysql查询一行数据也很慢?

在使用mysql的过程中,当遇到查询语句执行缓慢时,你首先会想到什么?执行的sql语句很复杂,没有走索引?查询语句返回的数据量很大?或者,此时数据库实例自身负载压力很大或者ioutil很高。

当然这些因素都会导致一条查询语句执行效率降低。除了上面列举的三个之外,你还能想到其他导致查询变慢的原因吗?

下面小编结合日常工作中遇到的场景以及学习资料,总结了一些其他导致查询语句变慢的原因。总结来说分为两类:

1.查询长时间不返回。

2.查询慢。

为了方便下面描述,创建一下测试表:

CREATE TABLE `table1` (
  `id` int(11) NOT NULL,
  `a` int(11)  DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB;

创建表 table1,id为主键,a为普通数据列。

同时向表中插入10000行数据。

create procedure idata()
begin
	declare i int;
	set i=1;
	while(i<=10000)do
		insert into `table1` (id,a) values(i,i);
		set i=i+1;
	end while;
end;;
delimiter ;
call idata();

查询长时间不返回

查询长时间不返回的可能原因,有如下几种情况

1.等待MDL锁

MDL全名是:meatdata lock 也就是元数据锁,主要是为了保证"数据表的访问"和"数据表结构调整"之间的互斥,防止两者同时执行导致出现数据不一致的问题。

MDL锁机制是在msyql5.5引入的:

当对一个表进行增删改查时,会添加MDL读锁。

对一个表的表结构进行调整时,会添加MDL写锁。

读锁之间不互斥,因此多个线程可以同时对一张表进行增删改查。

读写锁之间,写锁之间是互斥的,用来保证变更表结构的安全性。

不过在mysql5.7中,修改了MDL的加锁策略,也就是我们常说的的online DDL。所以对于MDL锁阻塞其他sql语句执行的场景,在mysql 5.7及以上的版本中,比较难以复现。

不过在mysql5.7中可以用其他方式复现该场景:
在这里插入图片描述

sessionA通过 lock table 命令持有表 table1 MDL写锁,而sessonB的查询语句需要获取MDL读锁,所以sessionB会进入等待状态。

对于这类问题的处理方式,最终的解决方案就是找到持有MDL写锁的process id,然后把它kill掉。但是,通过show processlist 命令,能看到 sessionA的command列是sleep,并没有显示任何其持有锁的相关信息。

在这里插入图片描述

不过在mysql中,为了方便问题的排查,会有一些记录sql语句执行过程的相关表,在问题排查的过程中,可以借助这些表来协助问题的排查,比如像 sys库中的schema_table_lock_waits表,就记录了造成阻塞的process id。

在这里插入图片描述

要想使用这些方便问题排查的表信息,需要在mysql启动时设置参数 performance_schema 的值为 on。这样mysql才会记录相关的sql语句执行信息,不过记录这些信息也是有代价的,大概会损耗mysql 10%的性能。所以打开改参数前还是要三思的。

2.行锁

行锁是我们日常开发中经常遇到的一种锁,在数据行被修改的过程中,mysql会在该数据行上添加行锁,此时如果其他事务也需要在该数据行上添加行锁的话,那么后者就会被阻塞,直到前者释放行锁。

那么有哪些操作会添加行锁呢?

1.修改数据行,会在被修改的数据行上添加行锁。

2.当前读。也就是在普通的查询语句后添加for update,如:select * from table1 where id = 1 for update;

3.主动添加行锁,在普通查询语句后,添加 lock in share mode;如:select * from table1 where id = 1 in share mode.

如下所示的操作:
在这里插入图片描述
sessionB会一直阻塞,直到sessionA释放其占用的行锁,或者获取锁超时。

对于这种问题的处理,重点就是找到是哪个precess id占用着行锁,和查找占用MDL锁的process id的方式相同,这里不再赘述

3.并发查询

并发查询是指:mysql当前正在执行的语句。它和并发连接是完全不同的概念,并发连接是指:show processlist 的结果,也就是当前发出执行sql语句请求的连接

通常情况下一个mysql实例的并发连接数可以达到几千个,无非就是多占用一些内存(每个连接都需要一定的内存来完成sql语句的执行和数据传输等),但是对数据库实例影响不大。而并发查询如果过多的话,会直接影响cpu的使用率。

并发查询计数的阈值由参数 innodb_thread_concurrency控制。

也就是说, 一个mysql实例,最多可以同时执行 innodb_thread_concurrency个语句,如果待执行的sql很多,且比较耗时的话,而innodb_thread_concurrency又比较小,那么并发查询数,很快会被占满,导致很多待执行的sql语句被阻塞。

不过需要注意的是:如果正在执行sql语句的线程,由于等待锁而迟迟不能执行完毕,那么这个线程会释放占用的并发执行计数。这种设计是为了防止:由于一个占用锁的事务迟迟没有提交,导致其他与这个锁有冲突的查询全部被阻塞,导致并发查询计数很快被占满,使整个mysql实例处于不可用的状态,实际上mysql实例却处于一个很闲的状态。

但是,如果有些语句的确在执行,只不过执行的很慢,那么他占用的并发查询计数,是不会释放的,如果这样的语句很多的话,那么当并发查询计数被占满后,其他sql语句就只能等待了。

和等待锁时释放并发查询计数不同,如果sql语句是类似下面的语句:

select sleep(100) from table1;

虽然sql语句执行线程也在处于等待状态,但是却不会释放并发查询计数。

查询慢

在RR事务隔离级别下的长事务

大部分情况下,一条查询语句自身执行效率低的原因在于,查询语句没有使用合适的索引,导致查询语句执行过程中扫描了过多的数据行,导致查询语句执行较慢。

如果排除查询语句扫描行数过多的原因,你还能想到那些情况会导致查询语句执行慢的原因?下面就说一下,小编在工作中遇到的一个场景:仅仅扫描一行数据,但是执行也很慢的场景。

先说明下,我使用数据库的事务隔离级别是RR。

在一个事务内,多次执行:

select * from table1 where id =1;

时,sql语句执行时长在不断变长。

但是如果执行类似的业查询:

select * from table1 where id =1 for update;

sql语句执行时长却很短;

产生这个问题的原因主要是:我手动开启了一个事务后,忘记了提交,导致这个事务变成了一个长事务,在此期间,很多其他事务不断的更新 id=1 的数据行。而在RR事务隔离级别下,为了能够实现事务内"可重复读机制",每次数据更新都会记录 undo log ,方便从最新的数据回溯到某个事务开启时候的数据值。查询机制如下:
在这里插入图片描述

在长事务提交前,对id=1的数据行操作越多,中间的undo log就会变得越长,执行快照读就越耗时,而对于当前读的查询语句,会直接读取最新的数据值,不会执行回溯的操作,因此同样的业务查询,当前读执行的效率更快。

总结

上面这些"倒霉"情况,你在工作中有遇到吗,除此之外,你还遇到过哪些更"坑"的场景?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值