【MySQL】只查一行语句,执行慢的原因

Separes

关注

阅读 80

2022-04-14

只查一行语句,执行慢的原因

执行“查一行”,可能会出现的执行长时间不返回被锁住执行慢的情况。这其中涉及到了表锁、行锁和一致性读的概念。

一、长时间不返回

查询长时间不返回

查询长时间不返回时,大概率是表 t 被锁住了。接下来分析原因的时候,一般都是首先执行一下 show processlist 命令,看看当前语句处于什么状态。

有可能是行被锁住了吗? 为什么不太可能,因为这个SELECT不是 for update,因此即使有人占着行锁,SELECT只是返回已提交的数据即可,并不需要等到其他更新操作完成。

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

such as,

SELECT concat('KILL ',id,';') 
FROM information_schema.processlist p 
INNER JOIN  information_schema.INNODB_TRX x 
ON p.id=x.trx_mysql_thread_id 
WHERE db='test';

等MDL锁

等 MDL 锁,就是使用 show processlist 命令查看 Waiting for table metadata lock 的示意图。 Waiting for table metadata lock 状态示意图:

img

出现这个状态表示的是,现在有一个线程正在表 t 上请求或者持有 MDL 写锁,把 select 语句堵住了。

MDL锁

MDL锁(Metadata Lock),即元数据锁。元数据指的是描述数据的数据,对数据及信息资源的描述性信息,在数据库中元数据即数据字典信息,包括db,table,function,procedure,trigger,event等。

MySQL从 5.5版本开始引入MDL锁,MDL锁主要为了保证元数据的一致性(主要是保证DDL操作与DML操作之间的一致性),用于处理不同线程操作同一元数据对象的同步与互斥问题。

具体而言,MySQL引入MDL锁可以解决如下问题:

一是事务隔离问题,比如在可重复读隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,2次查询结果就会不一致,无法满足可重复读的要求。

二是数据复制问题,比如会话A执行了多条更新语句期间,另外一个会话B做了表结构变更并且先提交,就会导致slave在重做时,先重做alter,再重做update时就会出现复制错误的现象。

等flush

我在表 t 上,执行下面的 SQL 语句:

mysql> select * from information_schema.processlist where id=1;

可能原因是,不支持事务的存储引擎进行数据库备份时,锁住整个数据库。

查出来这个线程的状态是 Waiting for table flush:

img

这个状态表示的是,现在有一个线程正要对表 t 做 flush 操作。MySQL 里面对表做 flush 操作的用法,一般有以下两个:

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

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

但是正常这两个语句执行起来都很快,除非它们也被别的线程堵住了。

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

现在,复现一下这种情况,复现步骤如图所示:
在这里插入图片描述

等行锁

复现场景如下:
img

如果你用的是 MySQL 5.7 版本,可以通过 sys.innodb_lock_waits 表查到。

查询方法是:

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

直接断开这个连接。这里隐含的一个逻辑就是,连接被断开的时候,会自动回滚这个连接里面正在执行的线程,也就释放了 id=1 上的行锁。

二、查询慢

未加锁采用一致性读,若当前存在事务更新过多版本并未提交将导致从当前undo_log回退版本从而致使查询时间长。

请看以下例子:

在这里插入图片描述

两个语句的输出结果

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

复现步骤

在这里插入图片描述

你看到了,session A 先用 start transaction with consistent snapshot 命令启动了一个事务,之后 session B 才开始执行 update 语句。

session B 执行完 100 万次 update 语句后,id=1 这一行处于什么状态呢?你可以从下图中找到答案。

img

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

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

精彩评论(0)

0 0 举报