Innodb中的鎖
雖然比較擅長的是Oracle,但是公司使用的是MySQL數據庫,所以不得不對MySQL數據庫多研究一下。今天就談一下MySQL中的鎖。
談鎖之前,要明白爲什麼會有鎖這種東西。之所以有鎖,大部分情況下是爲了實現事務(transaction)之間的隔離,那麼事務之間有幾種隔離方式,各種隔離方式又是爲了達到什麼效果呢?先來說一下各種讀現象。
髒讀:即一個事務A讀取了事務B還沒有提交過的數據;
不可重複讀:即事務A在第一次按照某條SQL語句讀取後跟第二次按照相同的SQL語句讀取數據庫,這段時間內,事務B對這條SQL語句覆蓋的數據行進行了已提交的修改(update或delete),導致A前後兩次讀取數據不一樣;
幻讀:即事務A在第一次按照某條SQL語句讀取後跟第二次按照相同的SQL語句讀取數據庫,這段時間內,事務B插入了滿足SQL語句的數據行並提交了(insert),導致A前後兩次讀取數據不一樣;
針對以上幾種讀現象,就有了read-uncommitted,read-committed,repeatable read,
serializable read,這幾種事務隔離級別,下面是各種隔離級別可以防止的讀現象:
是否防止 | 髒讀 | 不可重複讀 | 幻讀 |
read-uncommitted | 否 | 否 | 否 |
read-committed | 是 | 否 | 否 |
repeatable read | 是 | 是 | 否 |
serializable read | 是 | 是 | 是 |
下面通過例子的形式介紹,切記以begin顯式開啓事務。雖然mysql使用的是repeatableread的隔離級別,但是爲了在一定程度上防止幻讀,使用了next key算法(下面將介紹)進行上鎖。
例如使用如下SQL語句建表
createtable ktest (id int primary key auto_increment, col1 int, col2 int, key(col1));
其中id列包含主鍵索引的列,col1爲包含非唯一索引的列,col2爲不包含任何索引的列。使用如下sql語句對數據表插入數據
insertinto ktest (col1, col2) values (1,2),(2,3),(2,4),(4,5),(8,9);
以下是col1上索引的大體情況:
(空白部分表示還沒有插入)
在利用非唯一鍵進行唯一匹配更新或刪除的時候,例如此時事務A執行updatektest set col2 = 10 where col1 = 2;
那麼除了兩個col1=2的地方需要上鎖之外,爲了確保在事務執行過程中不會出現幻讀,即不能說剛把這兩行數據修改完成但還沒有提交事務的情況下,其他事務就能能插入col1=2(例如此時事務B執行insert into ktest values (10,3,5);)的數據行,那麼需要在事務A執行update語句之前,在col1爲2(右邊的)到col1爲4之間,加一個“虛擬鎖”,保證這段時間內不會有col1=2的數據插入進來,但是這樣帶來了一個弊端,即col1=3的數據行也不能在A執行的期間插入。這種在兩個索引值之間加的“虛擬鎖”就是GAP鎖,加上原本就應該加上鎖的兩個col1=2的鎖,合起來就叫next key鎖。
當然如果第一個col1不是1,而是0的話也要在col1爲0到col1爲2(左邊的)之間,加上類似的“虛擬鎖”。這樣也會造成col1=1的數據行無法插入。
以下是事務A(左)執行上面sql語句時,事務B(右)試圖插入col1=3的截圖
圖一
相反,對於id這樣的唯一列,在這上面試圖用唯一匹配的方式更新一行數據的時候,由於id已經是唯一列了,因此不需要通過鎖的方式防止有相同的id插入,所以只會對匹配的行上鎖。例如在上述表中沒有其他事務時執行完insert into ktest values (10, 3, 5);
此時如果不採取默認提交(即用begin顯示開啓事務),同時在兩個事務中分別執行
transaction A:update ktest set col2 = 10 where id = 10;
transaction B:insert into ktest values (9, 1, 1);
兩個事務之間不會產生任何鎖定,截圖如下
圖二
雖然這種情況下不會產生因爲GAP而導致的事務阻塞,但是不代表使用唯一鍵就不會有GAP鎖。例如在兩個事務中分別執行
transaction A:update ktest set col2 = 10 where id < 10;
transaction B:insert into ktest values (7, 1, 1);
事務A不僅會跟已有的id(1,2,3,4,5,9)上鎖,5與9之間也會有GAP鎖,而導致事務B被堵住。當然如果執行insertinto ktest values (17, 1, 1);是沒有問題的,因爲id=17的情況不會被這種鎖機制包含;同理執行insert into ktest values (-17, 1, 1);仍然會被堵住。
當然由於innodb中鎖是基於索引實現的,如果在沒有索引的col2上執行如下語句,將會鎖住所有行
update ktest setcol2 = 10 where col2 =10
雖然GAP鎖可以在一定程度上防止幻讀,但是有些時候也會引發一些莫名其妙的錯誤,例如下面表格表示的兩個事務,比如在上面的例子中,即使transaction B 多次全表掃描,但是仍然看不到id爲7的數據行,可是用戶就是沒辦法在transaction B中插入id爲7的數據行。
當通過修改innodb_locks_unsafe_for_binlog參數使其等於1的時候,重啓mysql服務,可以禁用GAP鎖,下面的兩個事務也不會產生堵塞。(先左後右)
圖三
總結:
由此可見GAP鎖雖然可以控制事務的隔離級別,但是無可避免的降低了事務的併發量,在生產中可以考慮使用read-committed的事務隔離級別(類似Oralce),或者將innodb_locks_unsafe_for_binlog參數設置爲1,取消可重複讀隔離級別下的GAP鎖。
如果對索引結構不太熟悉可以參考之前我寫過的關於索引的文章
http://9305967.blog.51cto.com/9295967/1885949
相關圖片已經打包上傳,大家看不清可以下載
最後提前祝大家元旦快樂。
2016.12.30