MySQL如何使用鎖解決幻讀
MySQL
在REPEATABLE READ
級別解決了幻讀問題,解決方案有兩種,一種是MVCC
版本控制鏈,具體可以參考這個,[MVCC 多版本控制鏈],還有就是通過加鎖的方式。這篇文章簡要介紹一下MVCC
如何通過加鎖的方式來解決幻讀問題。
準備工作
還是一樣,先建立一張表,如下所示:
CREATE TABLE hero (
number INT,
name VARCHAR(100),
country varchar(100),
PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;
然後插入如下資料:
INSERT INTO hero VALUES (1, '劉備', '蜀'), (3, '諸葛亮', '蜀'), (8, '曹操', '魏'), (15, '荀彧', '魏'), (20, '孫權', '吳');
此時MySQL
中的示意圖簡化如下所示:
前置知識
MySQL中行鎖分為兩種,共享鎖,
S鎖,以及獨佔鎖,
X鎖。當使用
select * from ...或者是
select * from ... in share mode這兩種語法時,會對進行搜尋的記錄上加上
S鎖。當使用
select * from ... for update這個語法時就會對記錄加
X`鎖。
當一個事務獲取了一條記錄的S
鎖,其他事務可以獲取該記錄的S
鎖,但不可以獲取X
鎖;當一個事務獲取了一條記錄的X
鎖,其他事務不可以繼續獲取該條記錄的X
鎖或者S
鎖。
分析
Record Locks
當開啟一個事務,使用select * from
語句時,InnoDB
LOCK_REC_NOT_GAP
,具體是S
鎖還是X
鎖,具體視select
語句而定。例如現在查詢number
值為8的記錄,
select * from hero where number=8;
此時示意圖如下所示:
Gap Locks
接下來就涉及到了MySQL
如何利用加鎖來解決REPEATABLE READ
級別解決幻讀了。上面說了,當使用select
語句查詢時,InnoDB
會對記錄加記錄鎖,但這就出現一個問題了,這個事務執行第一次查詢時,另一個事務要插入的資料此時還不存在,這個事務就無法對這些幻影記錄加上記錄鎖啊,那該怎麼辦呢?為了解決這個問題,InnoDB
LOCK_GAP
,間隙鎖。例如,我們可以在number
值為8的那條記錄上新增一個gap
鎖,如下所示:
當一條記錄被加上gap
鎖時,就意味著不允許其他事務在number
值8的前面的間隙,即(3,8)這個區間內插入新的記錄。比如說,現在有另一個事務想要插入一條記錄,(4,"張飛",“蜀”)。此時,定位到該條記錄的下一條記錄的number
值為8,而這條記錄上又有一個gap
鎖,因此這個插入操作就會被阻塞住,直到擁有這個gap
鎖的事務提交之後,number
列的值在(3,8)中的新記錄才能被插入。
gap
鎖的主要作用就是為了防止插入幻影記錄,如果你對一條記錄加了gap
鎖,並不會限制其他事務對這條記錄繼續加記錄鎖或者gap
鎖。
Next-Key Locks
當我們既想鎖住某條記錄,又想阻止其他事務在該記錄前邊的間隙插入新紀錄,此時該怎麼辦呢?這個時候,InnoDB
又出現了一個新的鎖結構,LOCK_ORDINARY
,也被稱之為next-key
鎖,臨鍵鎖。它其實是record
鎖和gap
鎖的結合體。如下所示,給number
值為8的記錄加一個next-key
鎖。
總結
InnoDB
中存在著不同的鎖,當使用select
語句查詢某條記錄時,InnoDB
會對該記錄加記錄鎖,即record
鎖;- 當一個事務對某條記錄加上
gap
鎖時,另一個事務此時向這條記錄前面間隙插入新記錄的操作將會被阻塞; next-key
鎖,又稱為臨鍵鎖,是記錄鎖和間隙鎖的結合體。既鎖住某條記錄,又會將該記錄前面的間隙一同鎖住,解決了MySQL
在REPEATABLE READ
級別下的幻讀問題。