在之前的文章MySQL 常見死鎖場景 -- 并發(fā)Replace into導致死鎖介紹了由于二級索引 unique key 導致的 deadlock, 其實主鍵也是 unique 的, 那么同樣其實主鍵的 unique key check 一樣會導致死鎖.
主鍵 unique 的判斷在
row_ins_clust_index_entry_low => row_ins_duplicate_error_in_clust
對于普通的INSERT操作, 當需要檢查primary key unique時, 加 S record lock. 而對于Replace into 或者 INSERT ON DUPLICATE操作, 則加X record lock
這里check unique 的時候, 如果這里沒有這個 record 存在, 加在下一個 record上, 如果已經有一個 delete_mark record, 那么就加在這個 delete marked record 上.
例子 1
create table t1 (a int primary key); # 然后有三個不 session: session1: begin; insert into t1(a) values (2); session2: insert into t1(a) values (2); session3: insert into t1(a) values (2); session1: rollback;
rollback 之前:
這個時候 session2/session3 會wait 在這里2 等待s record lock, 因為session1 執(zhí)行delete 時候會執(zhí)行row_update_for_mysql => lock_clust_rec_modify_check_and_lock
這里會給要修改的record 加x record lock
insert 的時候其實也給record 加 x record lock, 只不過大部分時候先加implicit lock, 等真正有沖突的時候觸發(fā)隱式鎖的轉換才會加上x lock
問題1: 這里為什么granted lock 里面 record 2 上面有x record lock 和 s record lock?
在session1 執(zhí)行 rollback 以后, session2/session3 獲得了s record lock, 在insert commit 時候發(fā)現死鎖, rollback 其中一個事務, 另外一個提交, 死鎖信息如下
這里看到 trx1 想要 x insert intention lock.
但是trx2 持有s next-key lock 和 trx1 x insert intention lock 沖突.
同時trx 也在等待 x insert intention lock, 這里從上面的持有Lock 可以看到 肯定在等待trx1 s next-key lock
問題: 等待的時候是 S gap lock, 但是死鎖的時候發(fā)現是 S next-key lock. 什么時候進行的升級?
這里問題的原因是這個 table 里面只有record 2, 所以這里認真看, 死鎖的時候是等待在 supremum 上的, 因為supremum 的特殊性, supremum 沒有gap lock, 只有 next-key lock
0: len 8; hex 73757072656d756d: asc supremum; // 這個是等在supremum 記錄
在 2 后面插入一個 3 以后, 就可以看到在record 3 上面是有s gap lock 并不是next-key lock, 如下圖:
那么這個 gap lock 是哪來的?
這里gap lock 是在 record 3 上的. 這個record 3 的s lock 從哪里來? session2/3 等待在record 2 上的s record lock 又到哪里去了?
這幾涉及到鎖升級, 鎖升級主要有兩種場景
insert record, 被next-record 那邊繼承鎖. 具體代碼 lock_update_insert
delete record(注意這里不是delete mark, 必須是purge 的物理delete), 需要將該record 上面的lock, 贈給next record上, 具體代碼 lock_update_delete
并且由于delete 的時候, 將該record 刪除, 如果有等待在該record 上面的record lock, 也需要遷移到next-key 上, 比如這個例子wait 在record 2 上面的 s record lock
另外對于wait 在被刪除的record 上的trx, 則通過 lock_rec_reset_and_release_wait(block, heap_no); 將這些trx 喚醒
具體看 InnoDB Trx lock
總結:
2 個trx trx2/trx3 都等待在primary key 上, 鎖被另外一個 trx1 持有. trx1 回滾以后, trx2 和 trx3 同時持有了該 record 的 s lock, 通過鎖升級又升級成下一個 record 的 GAP lock. 然后兩個 trx 同時插入的時候都需要獲得insert_intention lock(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION); 就變成都想持有insert_intention lock, 被卡在對方持有 GAP S lock 上了.
例子 2
mysql> select * from t1;
+—-+
| a |
+—-+
| 2 |
| 3 |
+—-+
然后有三個不同 session:
session1: begin; delete from t1 where a = 2; session2: insert into t1(a) values (2); session3: insert into t1(a) values (2); session1: commit;
commit之前
這個時候session2/3 都在等待s record 2 lock, 等待時間是 innodb_lock_wait_timeout,
commit 之后
在session1 執(zhí)行 commit 以后, session2/session3 獲得到正在waiting的 s record lock, 在commit 的時候, 發(fā)現死鎖, rollback 其中一個事務, 另外一個提交, 死鎖信息如下
trx1 等待x record lock, trx2 持有s record lock(這個是在session1 commit, session2/3 都獲得了s record lock)
不過這樣發(fā)現和上面例子不一樣的地方, 這里的record 都lock 在record 2 上, 而不是record 3, 這是為什么?
本質原因是這里的delete 操作是 delete mark, 并沒有從 btree 上物理刪除該record, 因此還可以保留事務的lock 在record 2 上, 如果進行了物理刪除操作, 那么這些record lock 都有遷移到next record 了
問題: 這里insert 操作為什么不是 insert intention lock?
比如如果是sk insert 操作就是 insert intention lock. 而這里是 s record lock?
這里delete record 2 以后, 由于record 是 delete mark, 記錄還在, 因此insert 的時候會將delete mark record改成要寫入的這個record(這里不是可選擇優(yōu)化, 而是btree 唯一性, 必須這么做). 因此插入就變成 row_ins_clust_index_entry_by_modify
所以不是insert 操作, 因此就沒有 insert intention lock.
而sk insert 的時候是不允許將delete mark record 復用的, 因為delete mark record 可能會被別的readview 讀取到.
通過GDB + call srv_debug_loop() 可以讓GDB 將進程停留在 session1 提交, 但是session2/3 還沒有進入死鎖之前, 這個時候查詢performance_schema 可以看到session2/3 獲得了record 10 s lock. 這個lock 怎么獲得的呢?
這個和上述的例子一樣, 這里因為等的比較久了, 所以發(fā)生了purge, 因為record 2 被物理刪除了. 因此發(fā)生了鎖升級, record 2 上面的record 會轉給next-record, 這里next-record 是10,
總結:
和上一個例子基本類似.
2 個trx trx2/trx3 都等待在primary key 上的唯一性檢查上, 鎖被另外一個 trx1 持有. trx1 commit 以后, trx2 和 trx3 同時持有了該 record 的 s record lock, 然后由于 delete mark record 的存在, insert 操作變成 modify 操作, 因此就變成都想持有X record lock, 被卡在對方持有 S recordlock 上了.
審核編輯:黃飛
-
死鎖
+關注
關注
0文章
25瀏覽量
8066 -
MySQL
+關注
關注
1文章
801瀏覽量
26439
原文標題:MySQL 常見死鎖場景-- 并發(fā)插入相同主鍵場景
文章出處:【微信號:inf_storage,微信公眾號:數據庫和存儲】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論