MySQL死锁检测机制参数innodb_deadlock_detect的设置
微信公众号中(这里)看到一个关于MySQL的innodb_deadlock_detect与并发相关的细节,觉得比较有意思,也即innodb_deadlock_detect这个参数的设置问题
开始之前,关于锁、死锁,我们要先统一下几点认知:
- 死锁是由于多个事务相互持有对方所需要的锁,结果导致事务都无法继续,进而触发死锁检测,其中某个事务会被回滚,释放相应的锁,其他事务得以正常继续;简言之,就是多个事务之间的锁等待产生了回路,死循环了;
- 死锁发生时,会立刻被检测到,并且回滚某个事务,而不会长时间阻塞、等待;
- 从MySQL 5.7.15开始,新增选项 innodb_deadlock_detect,没记错的话应该是阿里率先实现的功能。当它设置为 OFF 时(默认值是 ON),InnoDB会不检测死锁,在高并发场景(例如“秒杀”)业务中特别有用,可以提高事务并发性能;
- 在启用死锁检测时,InnoDB默认的最大检测深度为200,在上面提到的高并发高竞争场景下,热点数据上的锁等待队列可能很长,死锁检测代价很大。或者当等待队列中所有的行锁总数超过 100万 时,也会认为发生死锁,直接触发死锁检测;
- InnoDB行锁等待超时默认为50秒,一般建议设置5-10秒就够了;
- 有时候,可能会口误把 长时间的行锁等待 说成是 死锁,其实二者完全不一样,不要犯这种常识性口误。
死锁检测是一个MySQL Server层的自动检测机制,可以及时发现两个或者多个session间互斥资源的申请造成的死锁,且会自动回滚一个(或多个)事物代价相对较小的session,让执行代价最大的先执行。
该参数默认就是打开的,按理说也是必须要打开的,甚至在其他数据库中没有可以使其关闭的选项。
innodb_deadlock_detect
如果关闭innodb_deadlock_detect,也即关闭了死锁自动监测机制时,当两个或多个session间存在死锁的情况下,MySQL怎么去处理?
这里会涉及到另外一个参数:锁超时,也即innodb_lock_wait_timeout,该参数指定了“锁申请时候的最长等待时间”
官方的解释是:The length of time in seconds an InnoDB transaction waits for a row lock before giving up.
innodb_lock_wait_timeout默认值是50秒,也就是意味着session请求时,申请不到锁的情况下最多等待50秒钟,然后呢,就等价于死锁,自动回滚当前事物了?其实不是的,事情没有想象中的简单。
innodb_rollback_on_timeout
这里就涉及到另外一个参数:innodb_rollback_on_timeout,默认值是off,该参数的决定了当前请求锁超时之后,回滚的是整个事物,还是仅当前语句,
官方的解释是:InnoDB rolls back only the last statement on a transaction timeout by default。
默认值是off,也就是回滚当前语句(放弃当前语句的锁申请),有人强烈建议打开这个选项(on),也就是一旦锁申请超时,就回滚整个事物。
需要注意的是,默认情况下只回滚当前语句,而不是整个事物,当前的事物还在继续,连接也还在,这里与死锁自动监测机制打开之后会主动牺牲一个事物不同,锁超时后并不会主动牺牲其中任何一个事物。
这意味着会出现一种非常严重的情况,举个例子,可以想象一下如下这种情况:
关闭了死锁监测机制后,在innodb_rollback_on_timeout保持默认的off的情况下,session1和session2都是无法正常执行下去的,且永远都无法执行下去。
任意一个session出现锁超时,放弃当前的语句申请的锁,而不是整个事物持有的锁,当前session并不释放其他session请求的锁资源,
即便是继续下去,依旧如此,两者又陷入了相互等待,相互锁请求超时,继续死循环。
从这里可以看到,与死锁自动检测机制在发现死锁是主动选择一个作为牺牲品不同,一旦关闭了innodb_deadlock_detect,Session中的任意一方都不会主动释放已经持有的锁。
此时如果应用程序如果不足够的健壮,继续去申请锁(比如重试机制,尝试重试相关语句),session双方会陷入到无限制的锁超时死循环之中。
事实上推论是不是成立的?做个测试验证一下,数据库环境信息如下
模拟事物双方在当前语句的锁超时之后,继续申请锁,确实是会出现无限制的锁超时的死循环之中。
以上就比较有意思了,与死锁主动监测并牺牲其中一个事物不同,此时事物双方互不相让,当然也都无法成功执行。
这只不过是一个典型的负面场景,除此之外,还会有哪些问题值得思考?
1,因为事物无法快速提交或者回滚,那么连接持有的时间会增加,一旦并发量上来,连接数可能成为一个问题。
2,锁超时时间肯定要设置为一个相对较小的时间,但具体又设置为多少靠谱。
3,关闭死锁检测,带来的收益,与副作用相比哪个更高,当前业务类型是否需要关闭死锁检测,除非数据库中相关操作大部分都是短小事物且所冲突的可能性较低。
4,面对锁超时,应用程序端如何合理地处理锁超时的情况,是重试还是放弃。
5,与此关联的innodb_rollback_on_timeout如何设置,是保持默认的关闭(锁超时的情况下,取消当前语句的所申请),还是打开(锁超时的情况下,回滚整个事物)
在官方MYSQL 8.0 的文档中提到在高并发的系统中还是建议不使用 innodb_deadlock_detect
大部分文字都在重复一个观点,高并发使用死锁的检测,会引起性能的问题。
综上所述可推荐关于死锁以及事务回滚的MYSQL的配置:
1 innodb_deadlock_detect = off
2 innodb_lock_wait_timeout = 5
3 innodb_rollback_on_timeout = on
参考文章:
https://www.cnblogs.com/wy123/p/12724252.html