为什么我只查一行的语句,执行的还是这么慢?
有些情况,只查一行数据,执行的也会特别慢,接下来我们就梳理一下,会出现这里现象的场景。
数据库压力
如果MySQL数据库本身就有很大的压力,导致CPU占用率很高,IO利用率很高,那么即使是执行一条SQL语句,也会执行的很慢。
锁
如果这条SQL语句被锁住,他就会执行的很慢。而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();
全局锁
全局锁的执行语句是:
flush tables with read lock;
flush tables t with read lock;
如果指定了表t,代表的是只关闭表t,如果没有指定表名,则表示关闭MySQL里所有打开的表。
但是这个操作是加全局的写锁,使整个表处于只读的状态,不会阻塞查询语句。如果查询语句被阻塞,那么一定是这行命令被其他命令阻塞了,接下来我们来复现这个场景。
我们可以通过show processlist来查看语句执行状态:
可以看到 id为27,28的语句都id为26的语句被阻塞了,所以我们只要kill掉26号语句,即可解决这个问题,返回执行结果。
表级锁
如果有一个线程正在表t上请求或持有MDL写锁,就会把select语句阻塞住。
但是这种场景只能在 MySQL5.6版本复现,而MySQL5.7版本就修改了MDL加锁的策略,而是采用了Online DDL的方式对表结构进行修改(CopyOnWrite)所以这里我们可以使用给表加写锁的方式复现。
通过processlist来查看执行状态:
31pid的语句状态处于等待元数据锁。我们可以在MySQL启动时设置performance_schema=on;(相比设置为off会有10%左右的性能损失)我们可以直径通过sys.schema_table_lock_waits这张表,找到造成阻塞的process id;kill掉即可。
行锁
select * from t where id=1 lock in share mode;
如果我们访问id=1这个记录时,加了读锁,这个时候其他事物持有了写锁,我们的select语句就会被阻塞。
我们通过processlist可以查询到语句执行状态:
SessionA启动了事物,更改了数据,SessionB查询这条数据的时候,由于SessionA还没有提交事物。导致SessionB获取不到最新的数据(当前读)被阻塞。(读写锁阻塞)
接下来我们要找到是谁占着这个写锁,如果我们使用的MySQL版本是5.7,则可以通过sys.innodb_lock_waits查到。
select * from t sys.innodb_lock_waits where locked_table=`'test'.'t'`\G
kill掉相应线程即可。
慢查询
如果一个查询(当前读)伴随着一个长事务大量的修改,那么这个查询操作就会非常的慢。
SessionB的update操作,会产生大量的回滚日志,SessionA的
select * from t where id = 1;
是一致性读,即基于MySQL事务开启时,所产生的一致性视图。所以返回的结果是1,并且查询非常快。
而SessionA的
select * from t where id = 1 lock in share mode;
是当前读,需要等待SessoinB的100万次update操作,提交事物后,再根据回滚日志查询到当前值。
所以执行的就会非常慢。