Mysql的事务与锁知识(三) 之 死锁

前言

在我们使用锁的时候,有一个问题是需要注意和避免的,我们知道,排它锁有互斥的特性。一个事务或者说一个线程持有锁的时候,会阻止其他的线程获取锁,这个时候会造成阻塞等待,如果循环等待,会有可能造成死锁。
这个问题我们需要从几个方面来分析,一是锁为什么不释放,第二是被阻塞了怎么办,第三是死锁是怎么发生的,怎么避免。

死锁

1. 锁的释放与阻塞

锁什么时候释放?
事务结束(commit, rollback);客户端连接断开。
如果一个事务一直未释放锁,其他事务会被阻塞多久?会不会永远等待下去?如果是,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。

MySQL有一个参数来控制获取锁的等待时间,默认是50秒。

show VARIABLES like 'innodb_lock_wait_timeout';

在这里插入图片描述
对于死锁,是无论等多久都不能获取到锁的,这种情况,也需要等待50秒钟吗?那不是白白浪费了 50秒钟的时间吗?

我们先来看一下什么时候会发生死锁。

2. 死锁的发生和检测

死锁演示
案例1:

Session 1 Session 2
begin;
select * from t2 where id =1 for update;
begin;
delete from t2 where id =4 ;
update t2 set name= ‘4d’ where id =4 ;
delete from t2 where id =1;

案例2:

Session 1 Session 2
begin;
select * from t1 where id =1 lock in share mode;
begin;
select * from t1 where id =1 lock in share mode;
update t1 set name= ‘1a’ where id =1;
update t1 set name= ‘1a’ where id =1;

我们看到:在第一个事务中,检测到了死锁,马上退岀了,第二个事务获得了锁, 不需要等待50秒;

[Err] 1213 ・ Deadlock found when trying to get lock; try restarting transaction

为什么可以直接检测到呢?是因为死锁的发生需要满足一定的条件,所以在发生死锁时,InnoDB —般都能通过算法(wait-for graph)自动检测到。

死锁的产生条件(因为锁本身是互斥的):

  1. 同一时刻只能有一个事务持有这把锁;
  2. 其他的事务需要在这个事务释放锁之后才能获取锁,而不可以强行剥夺;
  3. 当多个事务形成等待环路的时候,即发生死锁。

这个也是表锁是不会发生死锁的原因,因为表锁的资源都是一次性获取的。

Session 1 Session 2
begin;
lock tables t2 write;
begin;
lock tables t1 write;
lock tables t1 write; // BLOCKED
//0K,上述语句不再阻塞,释放了 t2的表锁,获取到 t1的表锁 lock tables t2 write;
// OK,获取到 t2 的表锁

如果锁一直没有释放,就有可能造成大量阻塞或者发生死锁,造成系统吞吐量下降, 这时候就要查看是哪些事务持有了锁。

3. 查看锁信息(日志)

首先,SHOW STATUS命令中,包括了一些行锁的信息:

show status like 'innodb_row_lock_%';

在这里插入图片描述

  • lnnodb_row_lock_current_waits:当前正在等待锁定的数量;
  • lnnodb_row_lock_time :从系统启动到现在锁定的总时间长度,单位ms;
  • lnnodb_row_lock_time_avg :每次等待所花平均时间;
  • lnnodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间;
  • lnnodb_row_lock_waits :从系统启动到现在总共等待的次数。

SHOW命令是一个概要信息。InnoDB还提供了三张表来分析事务与锁的情况:

select * from information_schema.INNODB_TRX;--当前运行的所有事务,还有具体的语句

在这里插入图片描述

select * from information_schema.INNODB_LOCKS; -- 当前出现的锁

在这里插入图片描述

select * from information_schema.INNODB_LOCK_WAITS; -- 锁等待对应的关系

在这里插入图片描述
更加详细的锁信息,开启标准监控和锁监控:

set GLOBAL innodb_status_output=ON;
set GLOBAL innodb_status_output_locks=ON;

通过分析锁日志,找出持有锁的事务之后呢?
如果一个事务长时间持有锁不释放,可以kill事务对应的线程 ID,也就是 INNODB_TRX表中的trx_mysql_thread_id,例如执行kill4,kill7,kill8。
当然,死锁的问题不能每次都靠kil线程来解决,这是治标不治本的行为。我们应该尽量在应用端,也就是在编码的过程中避免。

有哪些可以避免死锁的方法呢?

4. 死锁的避免

  1. 在程序中,操作多张表时,尽量以相同的顺序来访问(避免形成等待环路);
  2. 批量操作单张表数据的时候,先对数据进行排序(避免形成等待环路);
  3. 申请足够级别的锁,如果要操作数据,就申请排它锁;
  4. 尽量使用索引访问数据,避免没有 where 条件的操作,避免锁表;
  5. 如果可以,大事务化成小事务;
  6. 使用等值查询而不是范围查询查询数据,命中记录,避免间隙锁对并发的影响。

猜你喜欢

转载自blog.csdn.net/nonage_bread/article/details/113114509