先构造一个表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的示意图:
这个状态表示:现在有一个线程正在表t上请求或者持有MDL写锁,把select语句堵住了。
MySQL5.7版本复现这个场景:
- SessionA通过lock table命令持有表t的MDL写锁。
- 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命令断开即可。
等flush
如果当前有线程正在对表t做flush操作,那么查询其对应的processlist信息如下:
select * from infomation_schema.preocesslist where id = 6;
查询结果:线程状态是 Waiting for table flush。
MySQL对表做flush有两个:
-- 指定表t,表示只关闭表t
flush tables t with read lock;
-- 没有指定具体表名,表示关闭MySQL里所有打开的表
flush tables with read lock;
可能出现 Waiting for table flush的情况:有一个flush tables命令被别的语句堵住了,然后它又堵住了我们其他select语句。
- sessionA执行select sleep(1) fron t,故意延长打开表时间。
- sessionB执行flush table t需要等待sessionA结束。
- sessionC要查询的话,会被sessionB的flush命令堵住。
对应的show processlist结果:
等行锁
有一个带读锁的查询select * from t where id =1 lock in share mode,如果这时已经有其他事务在这行记录上持有一个写锁,我们的select语句会被堵住。
复现如下:
sessionA启动了事务,占有写锁,还不提交,导致了SessionB被堵住。
【查询谁占用写锁?】
MySQL5.7版本,可以通过sys.innodb_lock_waits表查询到。
select * from t sys.innodb_lock_waits where locked_table = `'test'.'t'` \G;
可以看出是4号线程造成堵塞。 最后一行给出了“sql_kill_blocking_connection: KILL 4”的提示,就是说直接断开这个连接。断开这个连接,会自动回滚到这个连接里面正在执行的线程,也就释放了id=1上的锁。
第二类:查询慢
- 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)
【原因分析】
- sessionB更新100万次,生成了100万个回滚日志(undo log),select * from t where id=1这个语句,是一致性读,因此要从10000001开始依次执行undo log,执行100万次后,才将1这个结果返回。
- 带lock in share mode的SQL查询时当前读,会直接读到10000001这个结果。