19 | 为什么我只查询一行的语句,也会执行这么慢

221 阅读3分钟

先构造一个表t,在其中插入10W条数据。

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

第一类:查询长时间不返回

执行如下SQL,长时间不返回。

select * from t where id = 1;

出现这种情况,大概率表t被锁住了。一般是先执行一下show processlist命令,查看当前语句处于什么状态。

等MDL锁(表级别锁:元数据锁MDL(Meta Data Lock))

使用show processlist命令查看 Waiting for table metadata lock的示意图:

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

MySQL5.7版本复现这个场景:

image.png

  1. SessionA通过lock table命令持有表t的MDL写锁。
  2. SessionB查询需要获取MDL读锁,所以导致SessionB进入等待状态。

出现这类情况的处理方式:找到谁持有MDL写锁,然后把它kill掉。

show proceesslist的结果里面,SessionA的command列是"Sleep",没有具体的process id信息,那么如何获取造成阻塞的process id

  • MySQL启动时,设置performance_schema=on(比设置为off多10%左右性能损失)
  • 查询sys.schema_table_lock_waits这张表,可以直接找出造成阻塞的process id,把这个连接用kill命令断开即可。

image.png

等flush

如果当前有线程正在对表t做flush操作,那么查询其对应的processlist信息如下:

select * from infomation_schema.preocesslist where id = 6;

查询结果:线程状态是 Waiting for table flush。 image.png

MySQL对表做flush有两个:

-- 指定表t,表示只关闭表t
flush tables t with read lock;

-- 没有指定具体表名,表示关闭MySQL里所有打开的表
flush tables with read lock;

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

image.png

  • sessionA执行select sleep(1) fron t,故意延长打开表时间。
  • sessionB执行flush table t需要等待sessionA结束。
  • sessionC要查询的话,会被sessionB的flush命令堵住。

对应的show processlist结果: image.png

等行锁

有一个带读锁的查询select * from t where id =1 lock in share mode,如果这时已经有其他事务在这行记录上持有一个写锁,我们的select语句会被堵住。

复现如下: image.png sessionA启动了事务,占有写锁,还不提交,导致了SessionB被堵住。

image.png

【查询谁占用写锁?】

MySQL5.7版本,可以通过sys.innodb_lock_waits表查询到。

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

image.png

可以看出是4号线程造成堵塞。 最后一行给出了“sql_kill_blocking_connection: KILL 4”的提示,就是说直接断开这个连接。断开这个连接,会自动回滚到这个连接里面正在执行的线程,也就释放了id=1上的锁。

第二类:查询慢

image.png

  • sessionA先启动了一个事务,之后sessonB才开始执行update 语句。
  • sessionB执行完100万次update语句后,sessionA继续执行
  • sessionA执行效果
    • select * from t where id=1等待比较久(800ms)
    • select * from t where id=1 lock in share mode较快返回(0.2ms)

image.png 【原因分析】

  • sessionB更新100万次,生成了100万个回滚日志(undo log),select * from t where id=1这个语句,是一致性读,因此要从10000001开始依次执行undo log,执行100万次后,才将1这个结果返回。
  • 带lock in share mode的SQL查询时当前读,会直接读到10000001这个结果。