【MySQL】事务与锁(六):死锁问题分析 「爱情、让人受尽委屈。」 2022-11-01 04:24 187阅读 0赞 在我们使用锁的时候,有一个问题是需要注意和避免的,我们知道,排它锁有互斥的特性。一个事务或者说一个线程持有锁的时候,会阻止其他的线程获取锁,这个时候会造成阻塞等待,如果循环等待,会有可能造成死锁。 这个问题我们需要从几个方面来分析,一个是锁为什么不释放,第二个是被阻塞了怎么办,第三个死锁是怎么发生的,怎么避免。 ### 1.锁的释放与阻塞 ### 锁什么时候释放?事务结束(commit,rollback)或客户端连接断开。 如果一个事务一直未释放锁,其他事务会被阻塞多久?会不会永远等待下去?如果是,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。 [Err] 1205 - Lock wait timeout exceeded; try restarting transaction。 MySQL有一个参数来控制获取锁的等待时间,默认是50秒。 show VARIABLES like 'innodb_lock_wait_timeout'; ![在这里插入图片描述][20201101033659597.png_pic_center] 对于死锁,是无论等多久都不能获取到锁的,这种情况,也需要等待50秒钟吗?那不是白白浪费了50秒钟的时间吗?我们先来看一下什么时候会发生死锁。 ### 2.死锁的发生和检测 ### 死锁简单说就是,线程1拿A锁等B锁,同时线程2拿B锁等A锁。死锁演示如下: <table> <thead> <tr> <th align="left">Transaction1</th> <th align="left">Transaction2</th> </tr> </thead> <tbody> <tr> <td align="left">Begin;</td> <td align="left"></td> </tr> <tr> <td align="left">SELECT * FROM t2 where id=1 FOR UPDATE;</td> <td align="left"></td> </tr> <tr> <td align="left"></td> <td align="left">Begin;</td> </tr> <tr> <td align="left"></td> <td align="left">DELETE FROM t2 WHERE id=4;</td> </tr> <tr> <td align="left">UPDATE t2 SET name=‘4d’ WHERE id=4;</td> <td align="left"></td> </tr> <tr> <td align="left"></td> <td align="left">DELETE FORM t2 WHERE id=1;</td> </tr> </tbody> </table> 在第一个事务中,检测到了死锁,马上退出了,第二个事务获得了锁,不需要等待50秒: ![在这里插入图片描述][20201101033739673.png_pic_center] ![在这里插入图片描述][20201101033750411.png_pic_center] > 注:Transaction2 的 Delete id=1 很快也会进入死锁,因为 Transaction1 一直持有着 id=1 的锁 1)为什么可以直接检测到呢? 是因为死锁的发生需要满足一定的条件,所以在发生死锁时,InnoDB一般都能通过算法(wait-for graph)自动检测到。 2)那么死锁需要满足什么条件? 死锁的产生条件:因为锁本身是互斥的,所以 1. 同一时刻只能有一个事务持有这把锁 2. 其他的事务需要在这个事务释放锁之后才能获取锁,而不可以强行剥夺 3. 当多个事务形成等待环路的时候,即发生死锁 举例:理发店有两个总监。一个负责剪头的Tony总监,一个负责洗头的Kelvin总监。 1. Tony不能同时给两个人剪头,这个就叫互斥。 2. Tony在给别人在剪头的时候,你不能让他停下来帮你剪头,这个叫不能强行剥夺。 3. 如果Tony的客户对Kelvin总监说:你不帮我洗头我怎么剪头?Kelvin的客户对Tony总监说:你不帮我剪头我怎么洗头?这个就叫形成等待环路。 如果锁一直没有释放,就有可能造成大量阻塞或者发生死锁,造成系统吞吐量下降,这时候就要查看是哪些事务持有了锁。 ### 3.查看锁日志信息 ### SHOW STATUS命令中,包括了一些行锁的信息: show status like 'innodb_row_lock_%'; ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkzNTkyNw_size_16_color_FFFFFF_t_70_pic_center] * Innodb\_row\_lock\_current\_waits:当前正在等待锁定的数量; * Innodb\_row\_lock\_time :从系统启动到现在锁定的总时间长度,单位 ms; * Innodb\_row\_lock\_time\_avg :每次等待所花平均时间; * Innodb\_row\_lock\_time\_max:从系统启动到现在等待最长的一次所花的时间; Innodb\_row\_lock\_waits :从系统启动到现在总共等待的次数。 SHOW命令是一个概要信息。InnoDB还提供了三张表来分析事务与锁的情况: select * from information_schema.INNODB_TRX; -- 当前运行的所有事务 ,还有具体的语句 select * from information_schema.INNODB_LOCKS; -- 当前出现的锁 select * from information_schema.INNODB_LOCK_WAITS; -- 锁等待的对应关系 ![在这里插入图片描述][20201101033934714.png_pic_center] 找出持有锁的事务之后呢?如果一个事务长时间持有锁不释放,可以 kill 事务对应的线程 ID,也就是INNODB\_TRX表中的trx\_mysql\_thread\_id,例如执行kill 1314,kill 1316。 当然,死锁的问题不能每次都靠kill线程来解决,这是治标不治本的行为。我们应该尽量在应用端,也就是在编码的过程中避免。有哪些可以避免死锁的方法呢? ### 4.死锁的避免 ### 1. 在程序中,操作多张表时,尽量以相同的顺序来访问(避免形成等待环路) 2. 批量操作单张表数据的时候,先对数据进行排序(避免形成等待环路) 3. 申请足够级别的锁,如果要操作数据,就申请排它锁 4. 尽量使用索引访问数据,避免没有where条件的操作,避免锁表 5. 如果可以,大事务化成小事务 6. 使用等值查询而不是范围查询查询数据,命中记录,避免间隙锁对并发的影响 [20201101033659597.png_pic_center]: /images/20221024/ad324ab7a2124d288cac3c7fd1f427cb.png [20201101033739673.png_pic_center]: /images/20221024/234fecbf3cc54b949bca82316b153f38.png [20201101033750411.png_pic_center]: /images/20221024/f72f707834b544969deb2d8020912d19.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkzNTkyNw_size_16_color_FFFFFF_t_70_pic_center]: /images/20221024/0581208703b64e4097321d1a5b00fa2a.png [20201101033934714.png_pic_center]: /images/20221024/0b8d71e983fc4b37b973cc4143ebe098.png
还没有评论,来说两句吧...