咱們知道MySQL在可重複讀隔離級別下別的事物提交的內容,是看不到的。而可提交隔離級別下是能夠看到別的事務提交的。而若是咱們的業務場景是在事物內一樣的兩個查詢咱們須要看到的數據都是一致的,不能被別的事物影響,就使用可重複讀隔離級別。這種狀況下RR級別下的普通查詢(快照讀)依靠MVCC解決「幻讀」問題,若是是「當前讀」的狀況須要依靠什麼解決「幻讀」問題呢?這就是本博文須要探討的。html
在探討前能夠看下以前的博文(MySQL是如何實現事務隔離?),主要介紹隔離級別的具體技術細節,讀過之後看此篇文章可能更有幫助。session
注:本博文討論的「幻讀」都是指在「可重複讀」隔離級別下進行。併發
假設咱們有表t結構以下,裏面的初始數據行爲:(0,0,0),(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5)spa
CREATE TABLE `t` ( `id` INT(11) NOT NULL, `key` INT(11) DEFAULT NULL, `value` INT(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `value` (`value`) ) ENGINE = InnoDB; INSERT INTO t VALUES (0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)
假設select * from where value=1 for update,只在這一行加鎖(注意這只是假設),其它行不加鎖,那麼就會出現以下場景:日誌
Session A的三次查詢Q1-Q3都是select * from where value=1 for update,查詢的value=1的全部row。code
其中Q3讀到value=1這同樣的現象,就稱之爲幻讀,幻讀指的是一個事務在先後兩次查詢同一個範圍的時候,後一次查詢看到了前一次查詢沒有看到的行。orm
先對「幻讀」作出以下解釋:htm
(1)須要單獨解決blog
衆所周知,select ...for update語句就是將相應的數據行鎖住,好比session A在T1時刻的Q1查詢語句:select * from where value=1 for update就是將value=1的數據行鎖住,但顯然若是是上述的場景發生,此時的for update語義被破壞了(並無鎖住value=1的數據行)。事務
即便把全部的記錄都加上鎖,仍是阻止不了新插入的記錄,因此「幻讀」問題要單獨拿出來解決。無法依靠MVCC或者行鎖機制來解決。這就引出「間隙鎖」,是另一種加鎖機制。
(2)間隙鎖引起的併發度
間隙鎖引入之後,可能會致使一樣語句鎖住更大的範圍,這可能就會影響了併發度。具體請看下面介紹
產生幻讀的緣由是,行鎖只能鎖住行,可是新插入記錄這個動做,要更新的是記錄之間的「間隙」。所以,爲了解決幻讀問題,InnoDB只好引入新的鎖,也就是間隙鎖(Gap Lock)。
間隙:好比表中加入6個記錄,0,5,10,15,20,25。則產生7個間隙:
在一行行掃描的過程當中,不只將給行加上了行鎖,還給行兩邊的空隙也加上了間隙鎖。這樣就確保了沒法再插入新的記錄。
間隙鎖和行鎖合稱next-key lock,每一個next-key lock是前開後閉區間(間隙鎖開區間,next-key lock前開後閉區間):
間隙鎖與間隙鎖之間是不存在衝突的,衝突的是往間隙裏插入一條記錄。
表t中是沒有value=7這個數據的,因此Q1加的間隙鎖(1,5),而Q2也是加的這個間隙鎖,二者不衝突都是爲了保護這個間隙不容許插入值。
在表t初始化後,假設表的數據以下:
若是用select * from for update執行,則會把整個表全部記錄鎖起來,就造成了7個next-key lock,分別是(-∞,0]、(0,2]、(2,4]、(4,6]、(6,8]、(8, 10]、(10, +supremum]
間隙鎖的引入,可能會致使一樣的語句鎖住更大的範圍,是會影響了併發度
假設發生以下場景:
則明顯發生了死鎖,分析以下:
有上述可知間隙鎖的引入,可能會致使一樣語句鎖住更大的範圍,這實際上是影響了併發度。
爲了解決幻讀問題能夠採用讀可提交隔離級別,間隙鎖是在可重複讀隔離級別下才會生效的。因此若是把隔離級別設置爲讀提交的話, 就沒有間隙鎖了。但同時,你要解決可能出現的數據和日誌不一致問題,須要把binlog格式設置爲row,也就是說採用「RC隔離級別+日誌格式binlog_format=row」組合。