MySQL锁机制
MySQL死锁
### 死锁、活锁与锁饥饿概念 ##### 死锁(DeadLock) 死锁是指两个或两个以上的线程(或进程)在运行过程中,因为资源竞争而造成相互等待的现象,若无外力作用则不会解除等待状态,它们之间的执行都将无法继续下去。 ##### 活锁(LiveLock)是什么? 活锁是指正在执行的线程或进程没有发生阻塞,但由于某些条件没有满足,导致反复重试-失败-重试-失败的过程。与死锁最大的区别在于:活锁状态的线程或进程是一直处于运行状态的,在失败中不断重试,重试中不断失败,一直处于所谓的“活”态,不会停止。而发生死锁的线程则是相互等待,双方之间的状态是不会发生改变的,处于所谓的“死”态。 死锁没有外力介入是无法自行解除的,而活锁状态有一定几率自行解除。 ##### 锁饥饿(LockStarving) 锁饥饿是指一条长时间等待的线程无法获取到锁资源或执行所需的资源,而后面来的新线程反而“插队”先获取了资源执行,最终导致这条长时间等待的线程出现饥饿。 ReetrantLock的非公平锁就有可能导致线程饥饿的情况出现,因为线程到来的先后顺序无法决定锁的获取,可能第二条到来的线程在第十八条线程获取锁成功后,它也不一定能够成功获取锁。 如果你使用了多线程编程,但是在分配纤程组时没有合理的设置线程优先级,导致高优先级的线程一直吞噬低优先级的资源,导致低优先级的线程一直无法获取到资源执行,最终也会使低优先级的线程产生饥饿。 ### 死锁产生原因 诱发死锁的根本原因是`因为竞争资源引起的`。当然,产生死锁存在四个必要条件,如下: - ①互斥条件:指分配到的资源具备排他使用性,即在一段时间内某资源只能由一个执行实体使用。如果此时还有其它执行实体请求资源,则请求者只能等待,直至占有资源的执行实体使用完成后释放才行。 - ②不可剥夺条件:指执行实体已持有的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。 - ③请求与保持条件:指运行过程中,执行实体已经获取了至少一个资源,但又提出了新的资源请求,而该资源已被其它实体占用,此时当前请求资源的实体阻塞,但在阻塞时却不释放自己已获得的其它资源,一直保持着对其他资源的占用。 - ④环状等待条件:指在发生死锁时,必然存在一个执行实体的资源环形链。比如:线程T1等待T2占用的一个资源,线程T2在等待线程T3占用的一个资源,而线程T3则在等待T1占用的一个资源,最终形成了一个环状的资源等待链。 以上是死锁发生的四个必要条件,只要系统或程序内发生死锁情况,那么这四个条件必然成立,只要上述中任意一条不符合,那么就不会发生死锁。 ### 死锁处理 对于死锁的情况一旦出现都是比较麻烦的,但这也是设计并发程序避免不了的问题,当你想要通过多线程编程技术提升你的程序处理速度和整体吞吐量时,对于死锁的问题也是必须要考虑的一项,而处理死锁问题总的归纳来说可以从如下四个角度出发: - ①预防死锁:通过代码设计或更改配置来破坏掉死锁产生的四个条件其中之一,以此达到预防死锁的目的。 - ②避免死锁:在资源分配的过程中,尽量保证资源请求的顺序性,防止推进顺序不当引起死锁问题产生。 - ③检测死锁:允许系统在运行过程中发生死锁情况,但可设置检测机制及时检测死锁的发生,并采取适当措施加以清除。 - ④解除死锁:当检测出死锁后,便采取适当措施将进程从死锁状态中解脱出来。 ### MySQL中死锁如何解决呢? 在之前关于死锁的并发文章中聊到过,对于解决死锁问题可以从多个维度出发,比如预防死锁、避免死锁、解除死锁等,而当死锁问题出现后该如何解决呢?一般只有两种方案: - 锁超时机制:事务/线程在等待锁时,超出一定时间后自动放弃等待并返回。 - 外力介入打破僵局:第三者介入,将死锁情况中的某个事务/线程强制结束,让其他事务继续执行。 ##### MySQL的锁超时机制 在InnoDB中其实提供了锁的超时机制,也就是一个事务在长时间内无法获取到锁时,就会主动放弃等待,抛出相关的错误码及信息,然后返回给客户端。但这里的时间限制到底是多久呢?可以通过如下命令查询: ```sql show variables like 'innodb_lock_wait_timeout'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_lock_wait_timeout | 50 | +--------------------------+-------+ ``` 默认的锁超时时间是50s,也就是在50s内未获取到锁的事务,会自动结束并返回。那也就意味着当死锁情况出现时,这个死锁过程最多持续50s,然后其中就会有一个事务主动退出竞争,释放持有的锁资源,这似乎听起来蛮不错呀,但实际业务中,仅依赖超时机制去解除死锁是不够的,毕竟高并发情况下,50s时间太长了,会导致越来越多的事务阻塞。 那么咱们能不能把这个参数调小一点呢?比如调到1s,可以吗?当然可以,确实也能确保死锁发生后,在很短的时间内可以自动解除,但改掉了这个参数之后,也会影响正常事务等待锁的时间,也就是大部分未发生死锁,但需要等待锁资源的事务,在等待1s之后,就会立马报错并返回,这显然并不合理,毕竟容易误伤“友军”。 也正是由于依靠锁超时机制,略微有些不靠谱,因此InnoDB也专门针对于死锁问题,研发了一种检测算法,名为wait-for graph算法。 ##### 死锁检测算法 - wait-for graph 可以通过innodb_deadlock_detect=on|off这个参数,来控制是否开启死锁检测机制 wait-for graph算法被启用后,会要求MySQL收集两个信息: - 锁的信息链表:目前持有每个锁的事务是谁。 - 事务等待链表:阻塞的事务要等待的锁是谁。 每当一个事务需要阻塞等待某个锁时,就会触发一次wait-for graph算法,该算法会以当前事务作为起点,然后从「锁的信息链表」中找到对应中锁信息,再去根据锁的持有者(事务),在「事务等待链表」中进行查找,看看持有锁的事务是否在等待获取其他锁,如果是,则再去看看另一个持有锁的事务,是否在等待其他锁.....,经过一系列的判断后,再看看是否会出现闭环,出现的话则介入破坏。 ### 如何避免死锁产生? 因为死锁的检测过程较为耗时,所以尽量不要等死锁出现后再去解除,而是尽量调整业务避免死锁的产生,一般来说可以从如下方面考虑: - 合理的设计索引结构,使业务SQL在执行时能通过索引定位到具体的几行数据,减小锁的粒度。 - 业务允许的情况下,也可以将隔离级别调低,因为级别越低,锁的限制会越小。 - 调整业务SQL的逻辑顺序,较大、耗时较长的事务尽量放在特定时间去执行(如凌晨对账...)。 - 尽可能的拆分业务的粒度,一个业务组成的大事务,尽量拆成多个小事务,缩短一个事务持有锁的时间。 - 如果没有强制性要求,就尽量不要手动在事务中获取排他锁,否则会造成一些不必要的锁出现,增大产生死锁的几率。 - ........ 其实简单来说,也就是在业务允许的情况下,尽量缩短一个事务持有锁的时间、减小锁的粒度以及锁的数量。 同时也要记住:当MySQL运行过程中产生了死锁问题,那这个死锁问题以后绝对会再次出现,当死锁被MySQL自己解除后,一定要记住去排除业务SQL的执行逻辑,找到产生死锁的业务,然后调整业务SQL的执行顺序,这样才能从根源上避免死锁产生。
顶部
收展
底部
[TOC]
目录
MySQL锁机制的由来与分类
MySQL共享锁与排他锁
MySQL表锁
MySQL行锁
MySQL页面锁、乐观锁与悲观锁
MySQL死锁
MySQL锁机制的底层实现原理
相关推荐
MySQL教程
MySQL命令
MySQL索引
MySQL事务
MySQL版本特性