问题起因
自己做了个人造死锁的小实验:
Session1 | Session2 |
Begin; |
|
Select * from t where id=1 lock in share mode; |
|
| Begin; |
| Select * from t where id=1 lock in share mode; |
| Delete from t where id=1;//waiting |
Delete from t where id=1; //立刻报错:Deadlock found when trying to get lock; try restarting transaction |
|
按我对mysql的理解,死锁出现之后应该慢慢等他超时然后回滚,结果这里刚敲了执行立刻错就报出来了,显然不是用的超时;那我盲猜mysql会保存每个session持有的资源,每次运行新的sql语句会把所有session以持有的资源扫描一遍看看有没有可能造成死锁。但是emmm,这个策略感觉效率会很低。
于是很好奇mysql用了什么检测机制。
答案结论
5.7版本开始,innodb_deadlock_detect开关变量被引入且默认开启。(可以用show variables like '%innodb_deadlock_detect%';查看)
于是mysql有了两种途径检测死锁:原来的等待超时和新增的死锁检测。
死锁检测的原理是构建一个以事务为顶点、锁为边的有向图,判断有向图是否存在环,存在即有死锁,所以如果同时存在大量事务,那么检测或许会非常非常慢。因此mysql对此进行了上限控制。如果运行的时候报错
TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION
那就表示目前正在等待的事务数量已达到200上限。超过200个事务的等待列表被视为死锁,并且尝试检查等待列表的事务被回滚。如果锁定线程必须查看等待列表中事务拥有的超过1,000,000个锁,也可能发生相同的错误。(详见MySQL5.7文档)
顺便,无需担心死锁检测机制的代码会发生死锁,因为它无需等待资源,并不具备死锁发生的条件
搜寻答案过程中找到的其他有趣point
innodb执行事务的完全回滚时,事务设置的所有锁都会被释放。但是,如果错误导致仅回滚一条sql语句,则该语句设置的一些锁可能会被保留。发生这种情况是因为innodb以这样一种格式存储行锁:它在以后无法知道哪个锁是由哪个语句设置的。