mysql間隙鎖等值查詢,mysql間隙鎖實(shí)現(xiàn)原理?
我們都知道Mysql,Oracle PostgreSQL 可以利用MVCC來處理事務(wù),防止加鎖,來提高訪問效率
MVCC只是工作在兩種事務(wù)級(jí)別底下:(a) Read Committed (b) Repeatable Read;因?yàn)槠渌麅煞N:(c)READ UNCOMMITTED==》總是讀取最新的數(shù)據(jù),不符合當(dāng)前事務(wù)版本的數(shù)據(jù)行,(d)Serializable則會(huì)對(duì)所有的行加鎖。這兩種都不需要MVCC;參考:Mysql 的Innodb事務(wù)方面的 多版本并發(fā)控制如何實(shí)現(xiàn) MVCC這樣說來 Mysql 也跟其他的數(shù)據(jù)庫一樣,當(dāng) Repeatable Read的時(shí)候會(huì)出現(xiàn)幻讀的情況,其實(shí)不然,Mysql還有一種機(jī)制可以保證即使在Repeatable Read級(jí)別下面也不會(huì)出現(xiàn)幻讀;這就是間隙鎖:間隙鎖跟MVCC一起工作。實(shí)現(xiàn)事務(wù)處理:Repeatable Read隔離級(jí)別: 采用Next-key Lock(間隙鎖) 來解決幻讀問題.因此 Mysql 在Repeatable下面 幻讀,可重復(fù)讀,臟讀 三者都不會(huì)發(fā)生read committed隔離級(jí)別:采用Record鎖,不會(huì)出現(xiàn)臟讀,但是會(huì)產(chǎn)生"幻讀"問題. 也會(huì)出現(xiàn)可重復(fù)讀(我查了很久,這個(gè)read committed模式下也會(huì)出現(xiàn)可重復(fù)讀的問題參考:MySQL中Innodb的事務(wù)隔離級(jí)別和鎖的關(guān)系的講解教程)間隙鎖簡(jiǎn)介:MySQL InnoDB支持三種行鎖定方式:InnoDB的默認(rèn)加鎖方式是next-key 鎖。l 行鎖(Record Lock):鎖直接加在索引記錄上面,鎖住的是key。l 間隙鎖(Gap Lock):鎖定索引記錄間隙,確保索引記錄的間隙不變。間隙鎖是針對(duì)事務(wù)隔離級(jí)別為可重復(fù)讀或以上級(jí)別而已的。l Next-Key Lock :行鎖和間隙鎖組合起來就叫Next-Key Lock。默認(rèn)情況下,InnoDB工作在可重復(fù)讀(Repeatable Read)隔離級(jí)別下,并且會(huì)以Next-Key Lock的方式對(duì)數(shù)據(jù)行進(jìn)行加鎖,這樣可以有效防止幻讀的發(fā)生。Next-Key Lock是行鎖和間隙鎖的組合,當(dāng)InnoDB掃描索引記錄的時(shí)候,會(huì)首先對(duì)索引記錄加上行鎖(Record Lock),再對(duì)索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。加上間隙鎖之后,其他事務(wù)就不能在這個(gè)間隙修改或者插入記錄。 read committed隔離級(jí)別下Gap Lock在InnoDB的唯一作用就是防止其他事務(wù)的插入操作,以此防止幻讀的發(fā)生。Innodb自動(dòng)使用間隙鎖的條件:(1)必須在Repeatable Read級(jí)別下(2)檢索條件必須有索引(沒有索引的話,mysql會(huì)全表掃描,那樣會(huì)鎖定整張表所有的記錄,包括不存在的記錄,此時(shí)其他事務(wù)不能修改不能刪除不能添加)行鎖(Record Lock)記錄鎖其實(shí)很好理解,對(duì)表中的記錄加鎖,叫做記錄鎖,簡(jiǎn)稱行鎖。生活中的間隙鎖(Gap Lock)編程的思想源于生活,生活中的例子能幫助我們更好的理解一些編程中的思想。生活中排隊(duì)的場(chǎng)景,小明,小紅,小花三個(gè)人依次站成一排,此時(shí),如何讓新來的小剛不能站在小紅旁邊,這時(shí)候只要將小紅和她前面的小明之間的空隙封鎖,將小紅和她后面的小花之間的空隙封鎖,那么小剛就不能站到小紅的旁邊。這里的小紅,小明,小花,小剛就是數(shù)據(jù)庫的一條條記錄。他們之間的空隙也就是間隙,而封鎖他們之間距離的鎖,叫做間隙鎖。Mysql中的間隙鎖下表中(見圖一),id為主鍵,number字段上有非唯一索引的二級(jí)索引,有什么方式可以讓該表不能再插入number=5的記錄?圖一根據(jù)上面生活中的例子,我們自然而然可以想到,只要控制幾個(gè)點(diǎn),number=5之前不能插入記錄,number=5現(xiàn)有的記錄之間不能再插入新的記錄,number=5之后不能插入新的記錄,那么新的number=5的記錄將不能被插入進(jìn)來。那么,mysql是如何控制number=5之前,之中,之后不能有新的記錄插入呢(防止幻讀)?答案是用間隙鎖,在RR級(jí)別下,mysql通過間隙鎖可以實(shí)現(xiàn)鎖定number=5之前的間隙,number=5記錄之間的間隙,number=5之后的間隙,從而使的新的記錄無法被插入進(jìn)來。間隙是怎么劃分的?注:為了方面理解,我們規(guī)定(id=A,number=B)代表一條字段id=A,字段number=B的記錄,(C,D)代表一個(gè)區(qū)間,代表C-D這個(gè)區(qū)間范圍。圖一中,根據(jù)number列,我們可以分為幾個(gè)區(qū)間:(無窮小,2),(2,4),(4,5),(5,5),(5,11),(11,無窮大)。只要這些區(qū)間對(duì)應(yīng)的兩個(gè)臨界記錄中間可以插入記錄,就認(rèn)為區(qū)間對(duì)應(yīng)的記錄之間有間隙。例如:區(qū)間(2,4)分別對(duì)應(yīng)的臨界記錄是(id=1,number=2),(id=3,number=4),這兩條記錄中間可以插入(id=2,number=3)等記錄,那么就認(rèn)為(id=1,number=2)與(id=3,number=4)之間存在間隙。很多人會(huì)問,那記錄(id=6,number=5)與(id=8,number=5)之間有間隙嗎?答案是有的,(id=6,number=5)與(id=8,number=5)之間可以插入記錄(id=7,number=5),因此(id=6,number=5)與(id=8,number=5)之間有間隙的,間隙鎖鎖定的區(qū)域根據(jù)檢索條件向左尋找最靠近檢索條件的記錄值A(chǔ),作為左區(qū)間,向右尋找最靠近檢索條件的記錄值B作為右區(qū)間,即鎖定的間隙為(A,B)。圖一中,where number=5的話,那么間隙鎖的區(qū)間范圍為(4,11);間隙鎖的目的是為了防止幻讀,其主要通過兩個(gè)方面實(shí)現(xiàn)這個(gè)目的:(1)防止間隙內(nèi)有新數(shù)據(jù)被插入(2)防止已存在的數(shù)據(jù),更新成間隙內(nèi)的數(shù)據(jù)(例如防止numer=3的記錄通過update變成number=5)間隙鎖在InnoDB的唯一作用就是防止其它事務(wù)的插入操作,以此來達(dá)到防止幻讀的發(fā)生,所以間隙鎖不分什么共享鎖與排它鎖。 默認(rèn)情況下,InnoDB工作在Repeatable Read隔離級(jí)別下,并且以Next-Key Lock的方式對(duì)數(shù)據(jù)行進(jìn)行加鎖,這樣可以有效防止幻讀的發(fā)生。Next-Key Lock是行鎖與間隙鎖的組合,當(dāng)對(duì)數(shù)據(jù)進(jìn)行條件,范圍檢索時(shí),對(duì)其范圍內(nèi)也許并存在的值進(jìn)行加鎖!當(dāng)查詢的索引含有唯一屬性(唯一索引,主鍵索引)時(shí),Innodb存儲(chǔ)引擎會(huì)對(duì)next-key lock進(jìn)行優(yōu)化,將其降為record lock,即僅鎖住索引本身,而不是范圍!若是普通輔助索引,則會(huì)使用傳統(tǒng)的next-key lock進(jìn)行范圍鎖定!要禁止間隙鎖的話,可以把隔離級(jí)別降為Read Committed,或者開啟參數(shù)innodb_locks_unsafe_for_binlog。對(duì)于快照讀來說,幻讀的解決是依賴mvcc解決。而對(duì)于當(dāng)前讀則依賴于gap-lock解決。深層次的原理分析:
在MVCC并發(fā)控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當(dāng)前讀 (current read)。快照讀,讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。當(dāng)前讀,讀取的是記錄的最新版本,并且,當(dāng)前讀返回的記錄,都會(huì)加上鎖,保證其他事務(wù)不會(huì)再并發(fā)修改這條記錄。在一個(gè)支持MVCC并發(fā)控制的系統(tǒng)中,哪些讀操作是快照讀?哪些操作又是當(dāng)前讀呢?以MySQL InnoDB為例:快照讀:簡(jiǎn)單的select操作,屬于快照讀,不加鎖。(當(dāng)然,也有例外,下面會(huì)分析)select * from table where ?; 當(dāng)前讀:特殊的讀操作,插入/更新/刪除操作,屬于當(dāng)前讀,需要加鎖。select * from table where ? lock in share mode;select * from table where ? for update;insert into table values (…);update table set ? where ?;delete from table where ?;所有以上的語句,都屬于當(dāng)前讀,讀取記錄的最新版本。并且,讀取之后,還需要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,對(duì)讀取記錄加鎖。其中,除了第一條語句,對(duì)讀取記錄加S鎖 (共享鎖)外,其他的操作,都加的是X鎖 (排它鎖)。 MySQL/InnoDB定義的4種隔離級(jí)別:Read Uncommited可以讀取未提交記錄。此隔離級(jí)別,不會(huì)使用,忽略。Read Committed (RC)快照讀忽略,本文不考慮。針對(duì)當(dāng)前讀,RC隔離級(jí)別保證對(duì)讀取到的記錄加鎖 (record lock),存在幻讀現(xiàn)象。Repeatable Read (RR)快照讀忽略,本文不考慮。針對(duì)當(dāng)前讀,RR隔離級(jí)別保證對(duì)讀取到的記錄加鎖 (記錄鎖),同時(shí)保證對(duì)讀取的范圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),不存在幻讀現(xiàn)象。Serializable從MVCC并發(fā)控制退化為基于鎖的并發(fā)控制。不區(qū)別快照讀與當(dāng)前讀,所有的讀操作均為當(dāng)前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。Serializable隔離級(jí)別下,讀寫沖突,因此并發(fā)度急劇下降,在MySQL/InnoDB下不建議使用。