說明:
上面的數字都是源代碼中的關於各類鎖的位圖值:
LOCK_TABLE:16
LOCK_IX:1
LOCK_REC_NOT_GAP:1024
LOCK_WAIT:256
LOCK_REC:32
因此鎖@6表示的是
LOCK_REC |
LOCK_REC_NOT_GAP |
LOCK_X |
LOCK_WAIT = 1315
依次類推
這裏檢查死鎖的算法大致上說一下,無非是檢查有沒有造成等待環
事務B的鎖@8等待事務C的鎖@6,事務C的鎖@6在等待事務B的鎖@3,此時發現又繞回來了,那麼產生死鎖。
到這裏,死鎖的現象如何產生已經解釋清楚,可是,這是爲何呢?
這裏的疑問是:
在事務A提交以後,將事務B喚醒了,此時事務B的鎖@4爲REC NOTGAP X(1059),那麼此時這個事務又去檢查鎖的狀況,看看本身事務的鎖有沒有GRANT成功的,若是有則直接使用而且繼續執行,若是沒有則再加鎖,作這個檢查的函數爲lock_rec_has_expl,它作的事情是下面的檢查:
===========================================================
lock = lock_rec_get_first(block, heap_no);
while (lock) {
if
(lock->trx == trx
&& !lock_is_wait_not_by_other(lock->type_mode)
&& lock_mode_stronger_or_eq(lock_get_mode(lock),
precise_mode & LOCK_MODE_MASK)
&& (!lock_rec_get_rec_not_gap(lock)
|| (precise_mode & LOCK_REC_NOT_GAP)
|| heap_no == PAGE_HEAP_NO_SUPREMUM)
&& (!lock_rec_get_gap(lock)
|| (precise_mode & LOCK_GAP)
|| heap_no == PAGE_HEAP_NO_SUPREMUM)
&& (!lock_rec_get_insert_intention(lock))) {
return(lock);
}
lock = lock_rec_get_next(heap_no, lock);
}
=============================================================
這裏須要知足6個條件:
- 首先這個鎖是本身事務的
- 這個鎖不是處於等待狀態
- 當前鎖的類型與precise_mode是兼容的,precise_mode值是X鎖,由於這裏是要作刪除
- 當前鎖不是NOT GAP類型,或者要加的鎖類型是NOTGAP類型的,或者heapno爲1
- 當前鎖不是GAP類型,或者要加的鎖類型是GAP類型的,或者heapno爲1
- 當前鎖不是意向插入鎖
但此時發現1059(鎖@4)根本不知足第4點啊,由於它首先是NOTGAP鎖,同時heapno不是1,因此沒有找到,因此在外面又從新建立一個鎖,由於此時這行已經有鎖了,那麼它會建立一個REC WAIT X鎖(291),也就是鎖@8。
因此即便鎖@4不是處於等待狀態了,此時也不能直接執行呢,而是從新建立了一個鎖。此時致使了死鎖。
那麼如今問題又來了,從上圖能夠看到,這個時間序列沒有什麼特別的,或者特殊的一個交叉過程,從而是否是咱們能夠很容易的重現呢?僅僅經過開啓三個會話,都設置爲not autocommit的,由於須要將第一個事務A的提交放在事務B C的後面。
那麼開始了,建立相同的表,刪除同一行記錄。
事務A |
事務B |
事務C |
begin |
|
|
delete
刪除行數返回爲1
|
|
|
|
begin |
|
|
delete 阻塞 |
|
|
|
begin |
|
|
阻塞 |
commit |
|
|
|
觀察有沒有死鎖 其實並無死鎖 刪除行數返回爲0 |
|
|
|
刪除行數返回爲0 |
圖2
按說,上面這個圖與圖1沒有什麼區別,但沒有死鎖?爲何?
其實沒有死鎖是正常的,若是這樣就死鎖了,那mysql簡直不能用了!!!
看來仍是有區別的
正常模式下再作一次log分析,從log中看出了大問題......
再將上面詳細的加鎖圖在無死鎖模式下的狀況貼出來:
事務A |
事務B |
事務C |
開始 |
|
|
表的IX鎖 17 @1 |
|
|
二級索引行鎖X REC NOTGAP 1059 @2
檢查死鎖 沒事
|
|
|
聚簇索引行鎖X REC NOTGAP 1059 @7
檢查死鎖 沒事
|
|
|
|
表IX鎖 17 @3 |
|
|
二級索引記錄行鎖 REC X WAIT 291 @4
檢查死鎖,沒事
|
|
|
|
表IX鎖 17 @5 |
|
|
二級索引記錄行鎖 REC X WAIT
291 @6
檢查死鎖 沒事
|
|
wait.... suspend.... |
wait.... suspend.... |
commit |
|
|
|
wakeup this trx 將@4的WAIT去掉,成爲35 |
|
|
執行完成,提交 |
|
|
|
執行完成 |
圖3
此時發現,圖3其實與圖1是同樣的,那爲何圖3能夠正常執行完成,而圖1死鎖了呢?
但認真仔細看了以後,發現有很小的地方是不一樣的,圖3中的鎖@4加上的鎖是291(REC & X & WAIT),而圖1中加的鎖比它多了一個NOTGAP的鎖,鎖@6也是同樣的,圖3的事務A在提交而且喚醒了鎖@4以後,它的鎖類型爲REC+X(35),而圖1中的值也是比它多了一個NOTGAP鎖。
如今已經基本定位了問題所在,應該是NOTGAP搞的鬼。可是爲何會有差異呢?
此時還須要回到代碼中查看,經過日誌分析,發現2個在執行下面代碼時走了不一樣的路:
=======================================
if (prebuilt->select_lock_type != LOCK_NONE) {
ulint lock_type;
if (!set_also_gap_locks
|| srv_locks_unsafe_for_binlog
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED
|| (unique_search
&& !UNIV_UNLIKELY(rec_get_deleted_flag(rec, comp)))) {
goto
no_gap_lock;//直接路到下面
lock_typ
e = LOCK_REC_NOT_GAP;處
} else {
lock_type = LOCK_ORDINARY;
}
、
if (index == clust_index
&& mode == PAGE_CUR_GE
&& direction == 0
&& dtuple_get_n_fields_cmp(search_tuple)
== dict_index_get_n_unique(index)
&& 0 == cmp_dtuple_rec(search_tuple, rec, offsets)) {
no_gap_lock://標記
lock_type = LOCK_REC_NOT_GAP;
}
=======================================
這裏關鍵的分叉口就是在上面紅色字體部分,死鎖的時候走了
goto
no_gap_lock,而沒有出現死鎖的時候走的是
lock_type = LOCK_ORDINARY;,而
LOCK_ORDINARY表示的是0,什麼都沒有,因此這2條路的不一樣就是差1024(NOTGAP鎖)。
那麼從日誌中發現,走了第一條路是由於條件
(unique_search
&& !UNIV_UNLIKELY(rec_get_deleted_flag(rec, comp))是符合的。
rec_get_deleted_flag函數的做用是判斷這條記錄是否是已經打了刪除標誌。
如今豁然明白了,若是當前這條要加鎖的記錄尚未打刪除標誌,則加的鎖是NOTGAP類型的鎖,不然就不設置類型,那說明上面的圖1中事務A仍是有一個細節沒有畫出來,正由於這個細節與事務B發生了交叉,致使了事務B在作的時候尚未打了刪除標記,因此就加了NOTGAP鎖,因此致使後面的死鎖。
而正常狀況下,也就是圖2的測試,由於事務A已經完成了全部的操做,只等待提交,此時確定已經打了刪除標誌,則在加鎖時不會加NOTGAP鎖,因此就不會出現死鎖。
哎,用一句同事常說的話:我這下真的瞭然了,原來問題這麼複雜,mysql中的貓膩太多了。
那如今分析一下緣由吧:
如今已經肯定問題就是出如今上面代碼的判斷中,在上面代碼的上面還有一段註釋:
/* Try to place a lock on the index record; note that delete
marked records are a special case in a unique search. If there
is a non-delete marked record, then it is enough to lock its
existence with LOCK_REC_NOT_GAP. */
這說明了加NOTGAP鎖的意圖,說明上面代碼的判斷是專門作的,具體緣由就無從查起了,可是註釋中說這是一種特殊狀況,爲何呢?解決方式是把那2行直接去掉就能夠了(測試過不會出現死鎖了),但這個會不會是解決問題的根本緣由,還要等待官方人員的處理。
因此到這裏,把完整的死鎖圖貼上來: