MySql如何避免死锁

180 阅读5分钟

什么是死锁?

在数据库系统中,死锁是指两个或多个事务在执行过程中由于争夺资源而造成的一种僵局。具体来说,这些事务互相持有对方所需要的资源,并且都在等待对方释放自己所需要的资源,从而导致所有涉及的事务都无法继续执行下去。

死锁的一个简单例子

如下图创建了一个order

create table `order`(
  `id` int not null AUTO_INCREMENT,
  `order_no` int default null,
  `create_time` datetime default null,
  primary key(`id`),
  key `index_order` (`order_no`)
  )ENGINE=Innodb;

插入数据如下:

如下有两个事务 A 和 B:

事务A中使用当前读,查看order_no=1007的记录是否存在,同时事务B使用当前读,查看order_no=1008的记录是否存在,那么两个事务就会同时在范围为(1006,+∞]加上 next-key lock。我们可以通过下列语句查询事务中所加的锁:

select * from performance_schema.data_locks\G;

如图第一条所记录是加的表级锁意向锁,第二条所记录加的是行级锁 next-key lock。那么事务A和事务B继续往下执行插入语句分别插入order_no=1007和order_or=1008的记录,此时因为在区间(1006,+∞]被事务A和事务B同时加一把 next-key lock 所以事务A和事务B就会阻塞等待对方释放资源,又因为双方事务都被阻塞,所以事务不会别提交,锁也就不会被释放,最终形成死锁。

死锁的四个必要条件

根据 Coffman 条件,发生死锁需要满足以下四个条件:

  1. 互斥条件:至少有一个资源必须处于非共享模式,即只能被一个事务独占使用。
  2. 占有并等待条件:一个事务已经持有了至少一个资源,同时又申请新的资源,但该资源已被其他事务占有,因此当前事务必须等待。
  3. 不可剥夺条件:资源不能被强制从某个事务那里剥夺,只有当事务主动释放资源时,其他事务才能获取到该资源。
  4. 循环等待条件:存在一个事务等待环,其中每个事务都在等待下一个事务所持有的资源。

如果上述四个条件同时成立,则可能发生死锁。

如何避免死锁

死锁的四个必要条件:互斥条件占有并等待条件不可剥夺条件循环等待条件。只要系统发生死锁,这四个条件必然成立,所以我们只需要破坏这四个条件中的一个就能够解决死锁问题。

在数据库层面有两种策略,通过打破循环等待来避免死锁:

  • 设置事务等待锁超时时间:当一个事务等待时间超过该值后,就会回滚该事务,于是锁就被释放了,另一个事务就继续执行。在 innodb 中通过参数 innodb_lock_wait_timeout 设置超时时间默认是50s。当发生超时时,就会出现一下提示:

  • 开启主动死锁检测:开启主动死锁检测后,innodb会在内部维护一个等待图(wait-for graph)图中的点表示事务边表示等待关系。innodb通过 使用**深度优先搜索(DFS, Depth-First Search)**算法遍历等待图,来检测是否出现循环依赖(死锁),一但检测到死锁 innodb 选着其中一个参与死锁的事务作为“牺牲品”,并回滚它来解除死锁状态。通常,InnoDB 会选择代价最小的事务进行回滚,即那个做了最少工作(执行了最少写操作)的事务。innodb通过参数 innodb_deadlock_detect ,控制是否开启自动检测死锁,默认情况为开启状态等于on。当检测到死锁就会出现下面提示:

面试官:开启主动死锁检测后如果出现一个死锁依赖链过长会出现什么情况?

当在MySQL中开启主动死锁检测(通过设置 innodb_deadlock_detect 为 on,这是默认设置),InnoDB存储引擎会主动尝试检测事务之间的死锁。如果出现一个死锁依赖链过长的情况,可能会导致以下几个方面的影响:

  1. 性能开销增加:死锁检测机制本身需要消耗一定的资源。对于较长的死锁依赖链,MySQL需要遍历这个链条上的所有事务以确定是否存在循环等待的情况。这会导致额外的CPU和内存使用,并可能对数据库的整体性能产生负面影响。
  2. 延迟增加:随着死锁依赖链的增长,识别死锁所需的时间也会增加。这是因为MySQL必须检查更多的事务来确认是否有循环等待发生。这种情况下,涉及到的事务将经历更长时间的等待,直到死锁被检测到并解决。
  3. 事务回滚:一旦检测到死锁,InnoDB会回滚其中一个或多个事务来打破死锁状态。通常会选择“代价较小”的事务进行回滚,即已经执行的工作量较少的事务。如果死锁依赖链很长,可能意味着更多事务受到影响,导致更多的回滚操作。
  4. 日志信息:每当检测到死锁并执行回滚时,MySQL会在错误日志中记录相关信息。对于复杂的死锁情况,这些日志可以帮助诊断问题,但同时也可能变得庞大且复杂,增加了维护和分析的难度。