在使用 MySQL 時,我們有時會遇到這樣的報錯:“Deadlock found when trying to get lock; try restarting transaction”。
在 14.5.5.3 How to Minimize and Handle Deadlocks 中有這樣一句話:
Deadlocks are not dangerous. Just try again.
死鎖不危險,重試一下就行。
實際上這個建議非常實用。
我們回顧一下死鎖發生的四個條件:
- 資源的獨占性(在某一時刻,最多只能被一個事務訪問);
- 請求與保持(事務獲取到鎖后,不會主動釋放);
- 不可剝奪(事務無法獲取另一個事務擁有的鎖);
- 循環等待(即事務 A 獲取到鎖 Lock1,等待 Lock2,同時事務 B 獲取到鎖 Lock2,等待 Lock1,此時,事務 A 和 B 循環等待對方釋放各自需要的鎖)。
值得注意的是,InnoDB 在絕大部分錯誤發生時都不會回滾(如:等待鎖超時不會回滾);
只有一個例外,那就是發生死鎖時,InnoDB 會回滾一個影響最小的事務(直接破壞了上面的第 3 點,這個事務就成為了 victim)。此時我們只要重試一下這個 victim 事務,那么,所有的事務都會成功提交。
同時,14.5.5.3 How to Minimize and Handle Deadlocks 中另一段話很重要:
InnoDBuses automatic row-level locking. You can get deadlocks even in the case of transactions that just insert or delete a single row. That is because these operations are not really “atomic”; they automatically set locks on the (possibly several) index records of the row inserted or deleted.
insert / delete 一行也可能引發死鎖。
因為這些操作不是原子性的,它們在執行中會加 index record lock。
例子很好構造。
比如有一張表:create table t1 (id int PRIMARY KEY, name char(20) key);
有兩個 session 並發:
session 1:
insert into t1 (id, name) values(5, 'Branden');
session 2:
select * from t1 where name > ‘B’ and id >3;

session1 訪問 name>'B',會在 Ben、Bob上加 X 鎖,並且在 Alex 與 Ben、Ben 與 Bob、Bob 與 Cathy 間加 Gap lock,在聚簇索引 id 為 1、3上加X鎖;接下來要獲取聚簇索引上 id 為 6、7的 X 鎖,以及 (3, 6)、(6, 7)間的 gap lock。
session2 先獲取聚簇索引上(3, 6)間的 gap lock;接下來嘗試獲取 name 索引上(Ben, Bob)、(Bob, Cathy)間的 gap lock。
最后發生死鎖。
怎么消除呢?讓 select 中 where 子句先訪問 id ,再訪問 name。
可以閱讀這篇文章:http://hedengcheng.com/?p=771#_Toc374698321。
另外,有必要了解各種語句需要的鎖:14.5.3 Locks Set by Different SQL Statements in InnoDB。
參考資料:
MySQL 5.7 Reference Manual:
14.5.2.3 Consistent Nonlocking Reads
Stack Overflow:
How to avoid mysql 'Deadlock found when trying to get lock; try restarting transaction'
Working around MySQL error “Deadlock found when trying to get lock; try restarting transaction”
理解innodb的鎖(record,gap,Next-Key lock)
MySQL InnoDB鎖機制之Gap Lock、Next-Key Lock、Record Lock解析
