MySQL死锁等待与超时事务回滚

MySQL InnoDB death lock timeout and transaction rollback

Posted by alovn on October 31, 2021

死锁

死锁是指两个或两个以上的事务在执行过程中因争夺资源而造成的一种互相等待的现象。解决死锁的最简单的方式就是不要等待,将等待转换为回滚,但是这样会导致资源浪费、并发性能的下降。

MySQL中采用了超时机制解决死锁:当两个事务互相等待的时候,当一个等待时间超过设置的阈值时,其中一个事务进行回滚,另一个事务则继续进行。

除了超时机制,此外InnoDB还根据锁的信息链表和事务等待链表实现了一种更为主动的死锁检测机制 wait-for graph(等待图),若发现存在死锁,通常会选择回滚undo量最小的事务。

参数

InnoDB引擎使用参数innodb_lock_wait_timeout来设置超时时间(默认时间是50秒),参数innodb_rollback_on_timeout用来设置是否在等待超时时对进行中的事务进行回滚操作(默认0,不回滚)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> select @@innodb_rollback_on_timeout;
+------------------------------+
| @@innodb_rollback_on_timeout |
+------------------------------+
|                            0 |
+------------------------------+
1 row in set


mysql> select @@innodb_lock_wait_timeout;
+----------------------------+
| @@innodb_lock_wait_timeout |
+----------------------------+
|                         50 |
+----------------------------+
1 row in set

其中innodb_lock_wait_timeout可以在MySQL运行中动态修改,而innodb_rollback_on_timeout时静态的,不可动态修改。

1
2
3
4
5
mysql> set @@innodb_lock_wait_timeout=5;
Query OK, 0 rows affected

mysql> set @@innodb_rollback_on_timeout=on;
1238 - Variable 'innodb_rollback_on_timeout' is a read only variable

在并发较高的系统中,若innodb_lock_wait_timeout使用默认值50秒,那么当发生死锁时会占用较大的资源,此时可根据业务适当调小参数,比如设置为3或5,这样若检测到死锁则可快速失败,而不必长时间等待。

异常

默认情况下InnoDB存储引擎不会回滚超时引发的异常。其实InnoDB存储引擎在大部分情况下都不会对异常进行回滚,但是死锁除外,InnoDB发现死锁会马上回滚一个事务。但这依然是十分危险的,所以若没有启用锁超时回滚,我们必须在程序中捕获MySQL抛出的异常并判断是否需要commit 还是 rollback。而大部分情况下由于事务是没有完整执行,会破坏事务原子性,需要执行回滚。