記一次Mysql併發"死鎖",引出的問題及討論

這幾天,在查看文章時,發現了一個Mysql併發的問題,在一開始僅僅憑藉眼睛去查看時,並未發現問題及解決方法,因而咱們對其進行了具體實際操做和測試:

1、問題

一個事務內:insert記錄後根據字段p來update這條記錄,然而當出現併發操做的時候,update處會發生dead lock問題,把update改成id,就沒事了。sql

同一個表,高併發事務,事務內先插入一條記錄,再更新這條記錄: (1)若是更新的是惟一索引,有異常; (2)若是更新的是自增主鍵,就沒有異常; 畫外音:先不要被「dead lock」描述所迷惑,是死鎖問題,阻塞問題,仍是其餘異常,還另說。session

2、測試問題及復現

2.1 數據準備

create table t (id int(20) primary key AUTO_INCREMENT,cell varchar(20) unique)engine=innodb;
複製代碼

新建表: (1)存儲引擎是innodb,MySQL版本是5.6; (2)id字段,自增主鍵; (3)cell字段,惟一索引;併發

start transaction;
insert into t(cell) values(11111111111);
insert into t(cell) values(22222222222);
insert into t(cell) values(33333333333);
commit;
複製代碼

插入一些測試數據。高併發

2.2 session參數設置

設置事務隔離級別爲RR(repeatable read)測試

--設置手動提交
--設置事務隔離級別爲RR
set session autocommit=0;
set session transaction isolation level repeatable read;
複製代碼

2.3 模擬併發

多個終端session模擬併發事務 ui

start TRANSACTION;
INSERT INTO t(cell) VALUES(44444444);
UPDATE t set cell = 123 WHERE cell = 44444444 ;
ROLLBACK;
複製代碼
start TRANSACTION;
INSERT INTO t(cell) VALUES(5555555);
UPDATE t set cell= 456 WHERE cell = 5555555 ;
ROLLBACK;
複製代碼

在Navicat中開啓兩個窗口spa

  1. 窗口A,先啓動事務,並插入記錄;
  2. 窗口B,再啓動事務,也插入記錄;
  3. 窗口A,修改插入的記錄;
  4. 窗口B,也修改插入的記錄;

2.4 結果

奇怪的出現了!code

  • 當運行到事務1的update時,發生了等待!
  • 當運行到事務2的update時,發生了死鎖,自動回滾了

3、查詢問題

按道理,插入不衝突的記錄,而後修改這條記錄,行鎖不該該衝突呀?
惟一索引,主鍵索引怎麼會有差別呢?是否有關?是死鎖,仍是其餘緣由?
orm

3.1 根據show engine innodb status查詢

百思不得其解,那就先看看innodb status裏都有什麼吧,複製粘貼下來後查看: cdn

可見Transaction1與Transaction2 同時鎖住了同一部分,並且是locak_mode X rec bur not gap Record lock

這就很奇怪了,又不是間隙鎖引發的死鎖,第一次update爲何會等待呢,第二次update爲啥會死鎖呢?

不懂,就換個地方看看

3.2 查看innodb_locks表

經過查看information_schema庫中inndb_locks表,可看到,確實事務1和事務2,同時鎖住了一片數據區域,致使了數據的等待、死鎖,可是緣由呢?

因而再次換方法查看:

3.3 explain/desc sql語句

咦!發現了重大問題
爲何這裏rows竟然是6!
我update爲何會掃了全表??
我是加了索引的啊

找到問題了:update沒走索引,而是掃了全表!

4、解決問題

既然找到問題了,就看看如何解決,爲何update沒有走索引呢? 那咱們回頭再看看兩個update語句

UPDATE t set cell = 123 WHERE cell = 44444444 ;
UPDATE t set cell= 456 WHERE cell = 5555555 ;
複製代碼

看着是沒啥問題呀?

尋尋膩膩,冷冷清清,悽悽慘慘慼戚,終於,在查看錶時,發現了問題:

回頭看建表語句/表結構

create table t (id int(20) primary key AUTO_INCREMENT,cell varchar(20) unique)engine=innodb;
複製代碼

cell字段數據類型是varchar類型的,而咱們的update寫的是cell = 444444;

並未對數據加引號!而致使了update沒走索引,掃了全表

因而,咱們再從頭看看這個過程:

     在事務隔離級別爲RR(Repeat Read)下
       事務1的insert產生了一個插入意向鎖,事務2的insert也產生了一個插入意向鎖(不會被互相鎖住,由於數據行並不衝突)
       此時事務1再進行update語句,因未走索引,致使掃全表,而在掃到事務2插入那條數據時,行鎖與插入意向鎖衝突了,致使事務1須要等待事務2釋放插入意向鎖而進行等待。
       事務2在進行update時,也一樣須要掃全表,可是全表都被事務1的update鎖住了,事務2須要等待 等待事務2釋放插入意向鎖的 事務1 的行鎖 釋放,所以發生了死鎖

那解決方法就很簡單了,將語句改成:

UPDATE t set cell = "123" WHERE cell = "44444444" ;
UPDATE t set cell= "456" WHERE cell = "5555555" ;
複製代碼

便可解決死鎖/等待問題

5、引申問題

5.1 RC與RR的比較

5.1.1 表中包含歷史數據的測試

其實在進行測試時,也曾經懷疑過是否是由於RR的問題,改爲RC試試呢?

--將事務隔離級別改成RC
SET TRANSACTION ISOLATION LEVEL REPEATABLE COMMITTED;
複製代碼

修改後,對其進行相同的操做:

發現:事務1insert,事務2insert,事務1的update生效,事務2的update發生了等待

根據上文中咱們找到的問題,對其進行分析:

  • 事務1在進行update時,也是掃了全表,可是由於RC沒有間隙鎖,沒有插入意向鎖,所以事務1的update不會進行等待
  • 事務2在進行update時,須要等待事務1的update提交釋放鎖,所以發生了等待

獲得結論:

RC下不存在間隙鎖

5.1.2 表中不包含歷史數據的測試(表爲空)

對於RC和RR的比較,咱們對錶中數據採起了刪數據的方法繼續進行測試:

truncate table 
複製代碼

繼續對錶進行相同操做,結果:

  • RR下仍然是事務1等待,事務2死鎖
  • RC下倒是事務1與事務2都正常,未發生等待

究其緣由,RR下,事務1插入的數據,事務2能看到,所以在RR下,即便數據清空,事務1仍然鎖住了事務2插入的數據。
而在RC下,事務1插入的數據事務2看不到,事務2插入的數據事務1看不到,他們各自僅僅鎖住了本身插入的數據,所以能執行成功。

5.1.3 結論

拋開可重複讀和讀已提交他們在同一個事務中屢次讀可讀出的東西外,這次發現了他們其餘的不一樣:

  1. RC中不存在間隙鎖、一樣不存在屬於間隙鎖的插入意向鎖
  2. RR下,事務1插入的數據事務2 能看到,RC下事務1插入的數據事務2看不到

5.2 Mysql中間隙鎖與插入意向鎖

在這次過程當中,咱們發現了: 行鎖、間隙鎖、插入意向鎖。其中因不一樣的行爲產生了不一樣的鎖,而其意義、用處也是不一樣的:

5.2.1 間隙鎖(Gap Locks)

  1. 區間鎖, 僅僅鎖住一個索引區間(開區間,不包括雙端端點)。
  2. 在索引記錄之間的間隙中加鎖,或者是在某一條索引記錄以前或者以後加鎖,並不包括該索引記錄自己。好比在 一、二、3中,間隙鎖的可能值有 (∞, 1),(1, 2),(2, ∞)。
  3. 間隙鎖可用於防止幻讀,保證索引間的不會被插入數據

5.2.2 插入意向鎖(Insert Intention Locks)

  1. 插入意向鎖是一種Gap鎖,不是意向鎖,在insert操做時產生。
  2. 在多事務同時寫入不一樣數據至同一索引間隙的時候,並不須要等待其餘事務完成,不會發生鎖等待。
  3. 假設有一個記錄索引包含鍵值4和7,不一樣的事務分別插入5和6,每一個事務都會產生一個加在4-7之間的插入意向鎖,獲取在插入行上的排它鎖,可是不會被互相鎖住,由於數據行並不衝突。
  4. 插入意向鎖不會阻止任何鎖,對於插入的記錄會持有一個記錄鎖。

5.2.3 鎖的選擇

在咱們處理sql語句去執行時,不一樣的語句會選擇不一樣的鎖:

  • 若是更新條件沒有走索引,例如執行」update test set name=「hello」 where name=「world」;」 ,此時會進行全表掃描,掃表的時候,要阻止其餘任何的更新操做,因此上升爲表鎖。

  • 若是更新條件爲索引字段,可是並不是惟一索引(包括主鍵索引),例如執行「update test set name=「hello」 where code=9;」 那麼此時更新會使用Next-Key Lock。使用Next-Key Lock的緣由:

  1. 首先要保證在符合條件的記錄上加上排他鎖,會鎖定當前非惟一索引和對應的主鍵索引的值;

  2. 還要保證鎖定的區間不能插入新的數據。

  3. 若是更新條件爲惟一索引,則使用Record Lock(記錄鎖)。

相關文章
相關標籤/搜索