MySQL 事務(wù)隔離級別實現(xiàn)原理和鎖的關(guān)系
隔離級別
并發(fā)帶來的問題
臟讀(dirty read)
如果一個事務(wù)讀到了另一個未提交事務(wù)修改過的數(shù)據(jù),如果另一個事務(wù)發(fā)生了回滾,那么該數(shù)據(jù)就是臟數(shù)據(jù)。
不可重復(fù)讀(non-repeatable read)
如果一個事務(wù)只能讀到另一個已經(jīng)提交的事務(wù)修改過的數(shù)據(jù),并且其他事務(wù)每對該數(shù)據(jù)進(jìn)行一次修改并提交后,該事務(wù)都能查詢得到最新值,即一個事務(wù)里兩次查詢一個數(shù)據(jù)的結(jié)果不一樣。。
幻讀(phantom read)
如果一個事務(wù)先根據(jù)某些條件查詢出一些記錄,之后另一個事務(wù)又向表中插入了符合這些條件的記錄,原先的事務(wù)再次按照該條件查詢時,能把另一個事務(wù)插入的記錄也讀出來。
注意
臟讀側(cè)重是未提交事務(wù)的數(shù)據(jù)。 而不可重復(fù)讀和幻讀都是讀到了已提交的數(shù)據(jù),但不可重復(fù)讀重點在于update和delete,而幻讀的重點在于insert。
四種隔離級別

實現(xiàn)原理
MVCC
首先,在介紹實現(xiàn)原理之前先簡單的介紹一下MySQL的MVCC機(jī)制。
悲觀鎖和樂觀鎖
悲觀鎖
正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制(也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。
在悲觀鎖的情況下,為了保證事務(wù)的隔離性,就需要一致性鎖定讀。讀取數(shù)據(jù)時給加鎖,其它事務(wù)無法修改這些數(shù)據(jù)。修改刪除數(shù)據(jù)時也要加鎖,其它事務(wù)無法讀取這些數(shù)據(jù)。
樂觀鎖
相對悲觀鎖而言,樂觀鎖機(jī)制采取了更加寬松的加鎖機(jī)制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機(jī)制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對長事務(wù)而言,這樣的開銷往往無法承受。而樂觀鎖機(jī)制在一定程度上解決了這個問題。
樂觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個版本標(biāo)識,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個 “version” 字段來實現(xiàn)。讀取出數(shù)據(jù)時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當(dāng)前版本號,則予以更新,否則認(rèn)為是過期數(shù)據(jù)。
要說明的是,MVCC的實現(xiàn)沒有固定的規(guī)范,每個數(shù)據(jù)庫都會有不同的實現(xiàn)方式,這里討論的是InnoDB的MVCC。
MVCC在MySQL中的實現(xiàn)
所謂的MVCC(Multi-Version Concurrency Control ,多版本并發(fā)控制)指的就是在使用讀已提交(READ COMMITTD)、可重復(fù)讀(REPEATABLE READ)這兩種隔離級別的事務(wù)在執(zhí)行普通的SELECT操作時訪問記錄的版本鏈的過程,這樣子可以使不同事務(wù)的讀-寫、寫-讀操作并發(fā)執(zhí)行,從而提升系統(tǒng)性能。
SELECT時,讀取創(chuàng)建版本號<=當(dāng)前事務(wù)版本號,刪除版本號為空或>當(dāng)前事務(wù)版本號。 INSERT時,保存當(dāng)前事務(wù)版本號為行的創(chuàng)建版本號 DELETE時,保存當(dāng)前事務(wù)版本號為行的刪除版本號 UPDATE時,插入一條新紀(jì)錄,保存當(dāng)前事務(wù)版本號為行創(chuàng)建版本號,同時保存當(dāng)前事務(wù)版本號到原來刪除的行
快照讀與當(dāng)前讀
快照讀:就是select
select?*?from?table?….;
當(dāng)前讀:特殊的讀操作,插入/更新/刪除操作,屬于當(dāng)前讀,處理的都是當(dāng)前的數(shù)據(jù),需要加鎖。
select?*?from?table?where???lock?in?share?mode;
select?*?from?table?where???for?update;
insert;
update?;
delete;
ReadView
InnoDB在實現(xiàn)MVCC時用到的一致性讀視圖,即consistent read view,用于支持RC(Read Committed,讀提交)和RR(Repeatable Read,可重復(fù)讀)隔離級別的實現(xiàn)。具體實現(xiàn)可見
讀未提交Read Uncommitted
不做任何加鎖和MVCC操作。
讀提交Read Committed
對于讀操作,不加鎖,為快照讀,每次讀取都使用最新的事務(wù)版本號生成最新的ReadView。 對于寫操作,每次加行鎖(提交事務(wù)時才解鎖,并且更新數(shù)據(jù)庫的事務(wù)版本)。
這樣的話就解決了臟讀問題,只要事務(wù)沒提交,數(shù)據(jù)庫的事務(wù)版本號就不會更新,那么ReadView中的數(shù)據(jù)永遠(yuǎn)都是這個新事務(wù)之前的數(shù)據(jù)。
但是沒有解決不可重復(fù)讀的問題,因為一個事務(wù)內(nèi)每次查詢的ReadView版本不一致。
可重復(fù)讀Repeatable Read
對于讀操作,不加鎖,只有第一次讀取的時候才會生成一個ReadView。 對于寫操作,加臨鍵鎖Next-key鎖(行鎖+GAP間隙鎖),就是除了給當(dāng)行記錄加鎖,還會給當(dāng)行記錄周圍區(qū)間加間隙鎖。
ReadView不同的生成策略解決了不可重復(fù)讀的問題,由于一個事務(wù)內(nèi)用的都是第一次查詢的ReadView,所以查出來的數(shù)據(jù)都是一致的。
而Next-key鎖機(jī)制又在一定程度上解決了幻讀的問題,由于GAP鎖會把一些相鄰的區(qū)間也鎖上,那么插入時就會被阻塞,從而在一定程度上解決了幻讀的問題,但是又沒有完全解決,因為之后相距比較遠(yuǎn)的數(shù)據(jù)還是可以插入。
串行讀Serializable
悲觀鎖機(jī)制實現(xiàn) 對于讀操作,加讀鎖。 對于寫操作,加寫鎖。 讀讀不互斥,讀寫互斥,寫寫互斥。 由于讀寫互斥,完全解決了三個問題,但是并發(fā)度比較低。
產(chǎn)生Gap間隙鎖的條件
在可重復(fù)讀事務(wù)隔離級別下(該事務(wù)隔離級別間隙鎖才會生效)
普通索引
一定會產(chǎn)生間隙鎖
唯一索引
鎖定單行記錄
對于指定查詢某一條記錄的加鎖語句,如果該記錄不存在,會產(chǎn)生記錄鎖和間隙鎖,如果記錄存在,則只會產(chǎn)生記錄鎖
如:WHERE id = 5 FOR UPDATE;
鎖定多行記錄
對于查找某一范圍內(nèi)的查詢語句,會產(chǎn)生間隙鎖
如:WHERE id BETWEEN 5 AND 7 FOR UPDATE;
