記一次死鎖分析過程

### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
### The error may involve com.cgd.order.dao.OrderSaleMapper.updateSaleOrderStatus-Inline
### The error occurred while setting parameters
### SQL: update t_order set ORDER_STATUS = ? where ORDER_ID = ?

小盆友,若是你在日誌裏看到這個是否是像我同樣會有不少問號??
我一個只會寫增刪改查sql的低層次程序員有了滿奶子問號。
可是我相信啊:只要功夫深,李白碰到的老婆婆就能把鐵杵磨成針。html

1.首先是要了解一些除了增刪改查以外的數據庫基礎知識

從極客上找了門MySQL實戰,若是你也想買,請聯繫我,推薦人買有返現的。 mysql

這門課我是以爲很值,這兩天爲了解決這個死鎖又讀了一遍有關加鎖的章節,發現了一條命令啊,這個命令會輸出不少信息,有一節 LATESTDETECTED DEADLOCK,就是記錄的最後一次死鎖信息。程序員

show engine innodb status;

我就試着執行了下,好巧不巧跟那天死鎖日誌裏的sql同樣,不過也說明這死鎖問題挺頻繁的sql

------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-12-17 11:20:09 7fbe339f7700
//第一個事務
*** (1) TRANSACTION:
TRANSACTION 116609954, ACTIVE 1.214 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
LOCK BLOCKING MySQL thread id: 72830 block 64282
MySQL thread id 64282, OS thread handle 0x7fbe01f7d700, query id 1042364621 172.16.21.10 prod_orderdb updating
//以上亂七八糟的有事務的基礎信息 大小 行數等等  


//下邊一行是死鎖的其中一方的sql 解析一下ORDER_ID是表的主鍵
/* 276aedd616081752079448345e2ac7/0.1.3// */
update t_order set ORDER_STATUS = 4 where ORDER_ID = 234


//表示這個事務在等待的鎖信息
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:


//當前在等待表t_order上的 X鎖, 主鍵爲 8de0b6b3a773935b
RECORD LOCKS space id 1252 page no 10646 n bits 112 index `PRIMARY` of table `t_order` trx id 116609954 lock_mode X locks rec but not gap waiting

//n_fields 115 表示記錄有115列 
Record lock, heap no 10 PHYSICAL RECORD: n_fields 115; compact format; info bits 0

//第一列 基本就是主鍵了
 0: len 8; hex 8de0b6b3a773935b; asc      s [;;
 1: len 6; hex 000006ef0a51; asc      Q;;
 2: len 7; hex 76000006f723b0; asc v    # ;;
 3: len 16; hex 32303230313231323030333139363331; asc 2020121200319631;;
 4: len 4; hex 80000000; asc     ;;
 5: SQL NULL;
 6: len 4; hex 80000002; asc     ;;
 7: SQL NULL;
 8: len 4; hex 8000000b; asc     ;;
.
..省略1xx行
.
//第二個事務信息
*** (2) TRANSACTION:
TRANSACTION 116609912, ACTIVE 7.886 sec fetching rows
mysql tables in use 1, locked 1
36461 lock struct(s), heap size 3241512, 306688 row lock(s), undo log entries 12
MySQL thread id 72830, OS thread handle 0x7fbe339f7700, query id 1042364593 172.16.21.0 prod_orderdb Searching rows for update

//同理第二個sql
update t_order set ORDER_STATUS = 4 where EXT_ID = '232SA'

//當前事務持有的鎖
*** (2) HOLDS THE LOCK(S):
//持有N個S鎖
RECORD LOCKS space id 1252 page no 10646 n bits 112 index `PRIMARY` of table `t_order` trx id 116609912 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 115; compact format; info bits 0
 0: len 8; hex 8de0b6b3a7739342; asc      s B;;
 1: len 6; hex 000006f1aeff; asc       ;;
 2: len 7; hex 64000006ce20bb; asc d      ;;
 3: len 16; hex 32303230313231323030333139363036; asc 2020121200319606;;
 4: len 4; hex 80000000; asc     ;;
 5: SQL NULL;
 6: len 4; hex 80000002; asc     ;;
 7: SQL NULL;
 .
 .
 .
//++++++++++
Record lock, heap no 10 PHYSICAL RECORD: n_fields 115; compact format; info bits 0
 0: len 8; hex 8de0b6b3a773935b; asc      s [;;
 1: len 6; hex 000006ef0a51; asc      Q;;
 2: len 7; hex 76000006f723b0; asc v    # ;;
 3: len 16; hex 32303230313231323030333139363331; asc 2020121200319631;;
 4: len 4; hex 80000000; asc     ;;
 5: SQL NULL;
 .
 .
 .
 //++++++++++ 
此處還省略N多 //++++++++++之間的行,同時持有不少行的鎖

//等待的鎖
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
//一樣是等待X鎖  主鍵爲 8de0b6b3a773935b
RECORD LOCKS space id 1252 page no 10646 n bits 112 index `PRIMARY` of table `t_order` trx id 116609912 lock_mode X locks rec but not gap waiting
Record lock, heap no 10 PHYSICAL RECORD: n_fields 115; compact format; info bits 0
 0: len 8; hex 8de0b6b3a773935b; asc      s [;;
 1: len 6; hex 000006ef0a51; asc      Q;;
 2: len 7; hex 76000006f723b0; asc v    # ;;
 3: len 16; hex 32303230313231323030333139363331; asc 2020121200319631;;
 4: len 4; hex 80000000; asc     ;;
 5: SQL NULL;
 .
 .
 .

//數據庫選擇回滾成本最小的一個事務進行回滾
*** WE ROLL BACK TRANSACTION (1)
2.思考

兩條sql,對應兩條數據,八竿子打不着的兩條數據在更新的時候發生了死鎖數據庫

update t_order set ORDER_STATUS = 4 where ORDER_ID = 234
update t_order set ORDER_STATUS = 4 where EXT_ID = '232SA'

當前問題所處環境整理:app

  • 數據庫事務隔離級別:讀提交(READ COMMITTED)
  • 表:t_order(ORDER_ID :主鍵; EXT_ID :非主鍵,無索引,但惟一)
  • 兩條sql都是經過惟一條件篩選數據,但不是同一條數據

極客的課程確定細緻不到官網,因此我去官網搜了搜,由於英語不是很好,就搜了Lock關鍵字,把搜出來的文章挨個看了下,先看這個
https://dev.mysql.com/doc/ref...fetch

有條件的看原文
沒條件的看這 咱們只看讀提交部分:

    對於UPDATE或 DELETE語句, InnoDB僅對其更新或刪除的行持有鎖。
    MySQL評估WHERE條件後,將釋放不匹配行的記錄鎖。
    這大大下降了死鎖的可能性,可是仍然能夠發生。
    
    對於UPDATE語句,若是某行已被鎖定,則InnoDB 執行「semi-consistent」讀取,
    將最新的提交版本返回給MySQL,以便MySQL能夠肯定該行是否符合 WHERE條件 UPDATE。
    若是該行匹配(必須更新),則MySQL會再次讀取該行,這一次將InnoDB其鎖定或等待對其進行鎖定。

官網例子spa

CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB;
    INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
    COMMIT;

在這種狀況下,表沒有索引,所以搜索和索引掃描使用隱藏的彙集索引進行記錄鎖定(請參見第15.6.2.1節「彙集索引和二級索引」),而不是使用索引列。

假設一個會話UPDATE使用如下語句執行 :
    # Session A
    START TRANSACTION;
    UPDATE t SET b = 5 WHERE b = 3;
    
    
還假設第二個會話 UPDATE經過在第一個會話的語句以後執行如下語句來執行:
    # Session B
    UPDATE t SET b = 4 WHERE b = 2;
    
在InnoDB執行UPDATE,它首先爲每一行獲取一個排他鎖,而後肯定是否對其進行修改。若是InnoDB不修改該行,則釋放鎖。不然,InnoDB保留該鎖直到事務結束。這會影響事務處理,以下所示。

使用默認REPEATABLE READ 隔離級別時:
第一個UPDATE將在其讀取的每一行上得到一個寫(x-lock)鎖,而且不會釋放其中的任何一個:
    x-lock(1,2); retain x-lock
    x-lock(2,3); update(2,3) to (2,5); retain x-lock
    x-lock(3,2); retain x-lock
    x-lock(4,3); update(4,3) to (4,5); retain x-lock
    x-lock(5,2); retain x-lock
第二UPDATE個嘗試獲取任何鎖的塊(由於第一個更新在全部行上都保留了鎖),而且直到第一個UPDATE提交或回滾時才繼續執行:
    x-lock(1,2); block and wait for first UPDATE to commit or roll back


使用READ COMMITTED則有不一樣: 
第一個UPDATE將在讀取的每一行上獲取一個寫(x-lock)鎖,併爲未修改的行釋放x鎖:
    x-lock(1,2); unlock(1,2)
    x-lock(2,3); update(2,3) to (2,5); retain x-lock
    x-lock(3,2); unlock(3,2)
    x-lock(4,3); update(4,3) to (4,5); retain x-lock
    x-lock(5,2); unlock(5,2)
    
第二個UPDATE, InnoDB執行 「semi-consistent」讀取,將讀取的每一行的最新提交版本返回給MySQL,以便MySQL能夠肯定該行是否符合如下 WHERE條件 UPDATE:
x-lock(1,2); update(1,2) to (1,4); retain x-lock
x-lock(2,3); unlock(2,3)
x-lock(3,2); update(3,2) to (3,4); retain x-lock
x-lock(4,3); unlock(4,3)
x-lock(5,2); update(5,2) to (5,4); retain x-lock

可是,若是WHERE條件包括索引列並InnoDB使用索引,則在獲取和保留記錄鎖時僅考慮索引列。

看完了例子:雖然"semi-consistent讀取"的解釋還有點迷糊,可是個人死鎖問題已經大體有了眉目。
個人表EXT_ID就是沒有索引啊,explain了,確實是全表查的,也就是存在這個一個逐行加鎖並釋放鎖的過程。rest

固然後來又發現了另外一篇文檔
https://dev.mysql.com/doc/ref...日誌

有條件的看原文去
沒條件的繼續 :
如下示例說明了鎖定請求將致使死鎖時如何發生錯誤。該示例涉及兩個客戶端A和B。

首先,客戶端A建立一個包含一行的表,而後開始事務。
在事務中,A經過S lock 下選擇行來得到對行的鎖定:


mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)

mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM t WHERE i = 1 FOR SHARE;
+------+
| i    |
+------+
|    1 |
+------+


接下來,客戶端B開始事務並嘗試從表中刪除該行:
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> DELETE FROM t WHERE i = 1;

刪除操做須要一個X鎖,可是它沒法得到X鎖,由於A目前持有S鎖,兩個鎖不兼容 ,所以該請求進入針對行和客戶端B塊的鎖請求隊列中。

最後,客戶端A還嘗試從表中刪除該行:

mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

此處發生死鎖是由於客戶端A須要X鎖才能刪除該行。
可是,不能授予它X鎖,由於客戶端B已經有一個X鎖定請求,而且正在等待客戶端A釋放其S鎖定。  
因爲B事先要求鎖,因此A持有的S 鎖也不能升級X鎖。
結果, InnoDB爲其中一個客戶端生成錯誤並釋放其鎖。客戶端返回此錯誤:

ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
再以後就能夠授予對另外一個客戶端的鎖定請求,並從表中刪除該行。

我只能說這例子跟個人一毛同樣啊,早找到這篇文章我還查個屁的隔離級別。

官方文檔仍是牛B!!!還買什麼課啊!!看官網幹啥!愣着啊!!

相關文章
相關標籤/搜索