这篇文章主要记录,造成查询数量不大的情况下,造成查询缓慢的原因,以及相应的解决方法。

环境说明:

  1. MySQL 版本 5.7.27
  2. 隔离级别:RR

锁等待造成查询速度很慢

MDL 锁

如开启如下事务:

Session A Session B
lock table t write;
begin;
select * from t where id=1;

Session A 持有表 t MDL 写锁。Session B 需要 MDL 读锁。这时读写锁互斥,Session B 被阻塞。

flush 阻塞

flush 操作一般来说很快就能执行完,当通过查询进程状态后,看到被 flush 操作被阻塞,一般都是由其他语句引起的。

如下面事务:

Session A Session B Session C
select sleep(1) from t;
flush tables t;
select * from t where id =1;

Session A 在扫描每一行时会休眠1s,而 Session B 的 flush tables t; 需要关闭表 t,要等 Session A 结束。后面的 Session C 被 Session B 阻塞了。

flush 的操作示范:

#  flush 表 t
flush tables t with read lock;
# flush 所有表
flush tables with read lock;

flush 的作用在 全局锁 文章中已经介绍过,FTWRL 主要用于 MyISAM 这样不支持事务的引擎,保证在备份时视图数据一致性。

行锁

这里的行锁,用两阶段锁来体现。其他如间歇锁和 next-key 锁都会可以造成这样的现象。

Session A Session B
begin;
update t set c=c+1 where id=1 select * from t where id=1 lock in share mode;

Session A 拥有 id=1 这行的写锁,Session B 想要拥有这一行的读锁,读写锁互斥。

解决方法

首先通过 show processlist; 命令查询被阻塞的状态信息。如需进一步分析的话,可以将 performance_schema=on 打开,通过查询 select blocking_pid from sys.schema_table_lock_waits; 具体造成锁等待的原因。然后 kill掉相应的 session.

在打开 performance_schema 会有一定的性能损失。

查询确实慢

没有设置合适的索引

如果没有设置合适的索引,导致扫描行数过多,时间自然就慢了。对于这种情况,可以开启慢查询日志,查看语句的执行过程,然后进行分析。

默认情况,慢查询日志时关闭的,打开方式如下:

# 查询慢查询日志状态及存储位置
# show variables  like '%slow_query_log%';

# 查询慢查询日志的设置时间
show variables  like '%long_query%';

# 临时打开慢查询日志,MySQL 重启后失效
set global slow_query_log=1;

# 改变时间
set long_query_time=0;

事务隔离的影响

事务究竟有没有被隔离这篇中,我们知道表中的每行数据都有多个版本,在一致性视图开启后,视图的一致性读的结果就是通过和数据行的版本比较进而显示的结果。

Session A Session B
start transaction with consistent snapshot;
update t set c=c+1 where id=1
update set c=c+1 where id=1; // 100 万次
select * from t where id=1;
select * from t where id=1 lock in share mode;

这时第一个 select 语句就会比第二个加锁的 select 语句还要慢。

因为第一个 select 语句是一致性读,需要从 100 万条回滚日志中比较直到找到适合的版本。

而第二个 select 语句是当前读,直接读取最新版本就可以了。所以花费的时间不一样。

总结

造成查询小数据量,却很缓慢的原因一般有两种,第一种可能是所查数据被锁住。另一种确实是查找过程是真的很慢。

对于数据被锁住的情况,一般会由 MDL 锁,FLUSH 操作被阻塞,行锁造成。

对于查询确实很慢来说,考虑下索引是否设置的合适。并注意在 RR 级别下,是否由于一致性读和当前读的不同而造成查询速度不一致的情况。

img

在分析原因时,可以通过进程状态以及 sys.innodb_lock_waits 中的信息,来做出相应的处理。

参考

https://dev.mysql.com/doc/refman/5.7/en/flush.html

https://dev.mysql.com/doc/internals/en/flush-tables.html

版权声明:本文为michael9原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/michael9/p/12330392.html