MySQL的事務(wù)機(jī)制和鎖(InnoDB引擎、MVCC多版本并發(fā)控制技術(shù))
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
? 作者?|??Life_Goes_On?
來源 |? urlify.cn/qmmYry
66套java從入門到精通實(shí)戰(zhàn)課程分享
一、事務(wù)(數(shù)據(jù)庫(kù)的事務(wù)都通用的定義)
1.1 事務(wù)定義
事務(wù)是由一步或幾步數(shù)據(jù)庫(kù)操作序列組成邏輯執(zhí)行單元,這系列操作要么全部執(zhí)行,要么全部放棄執(zhí)行。事務(wù)通常以?BEGIN TRANSACTION?開始,以COMMIT?或?ROLLBACK?操作結(jié)束:
COMMIT?即提交,提交事務(wù)中所有的操作、事務(wù)正常結(jié)束;ROLLBACK?即回滾,撤銷已做的所有操作,回滾到事務(wù)開始的狀態(tài)。
1.2 事務(wù)的四種特性
ACID:原子性,一致性,隔離性,持久性。
| 原子性(Atomicity) | 指事物在邏輯上是不可分割的操作單元,所有語句要么都執(zhí)行,要么都撤銷執(zhí)行。 |
| 一致性(Consistent) | 一個(gè)事務(wù)本質(zhì)是將數(shù)據(jù)從一種一致性狀態(tài)轉(zhuǎn)換到另一種一致性狀態(tài),具體取決于現(xiàn)實(shí)生活的邏輯。(比如轉(zhuǎn)賬,A轉(zhuǎn)給B,操作前后A+B的錢是不變的) |
| 隔離性(Isolation) | 隔離性是針對(duì)并發(fā)事務(wù)而言的,同時(shí)處理多個(gè)事務(wù)的時(shí)候,數(shù)據(jù)庫(kù)的事務(wù)提供了不同的隔離級(jí)別來保證正確。 |
| 持久性(Durable) | 事務(wù)一旦提交,對(duì)于數(shù)據(jù)的修改是持久性的,數(shù)據(jù)更新的結(jié)果已經(jīng)從內(nèi)存轉(zhuǎn)存到外部存儲(chǔ)器,即使系統(tǒng)故障,已提交的數(shù)據(jù)更新也不會(huì)丟失。 |
這四個(gè)特性在沒有并發(fā)的時(shí)候顯然很容易滿足,但是在并發(fā)處理事務(wù)的情況下,可能會(huì)帶來一些問題
| 丟失更新(Lost Update) | 當(dāng)兩個(gè)或多個(gè)事務(wù)操作同一行,后面的事務(wù)修改的值會(huì)覆蓋前面的事務(wù)修改的值。 |
| 臟讀(Dirty Reads) | 一個(gè)事務(wù)讀到了被另一個(gè)事務(wù)修改,但尚未提交的事務(wù)。當(dāng)一個(gè)事務(wù)正在多次修改一個(gè)數(shù)據(jù),而這一系列修改還沒有最后提交,另一個(gè)并發(fā)事務(wù)來讀取了,就會(huì)導(dǎo)致錯(cuò)誤。也就是另一個(gè)事務(wù)讀到了臟數(shù)據(jù)。 |
| 不可重復(fù)讀(Non-Repeatable Reads) | 一個(gè)事務(wù)操作的過程里,先讀取一個(gè)數(shù)據(jù),后來又讀取,而兩次讀出的數(shù)據(jù)值不一致。就是因?yàn)橹虚g被別的事務(wù)修改了。 |
| 幻讀(Phantom Reads) | 一個(gè)事務(wù)按照相同的查詢條件查兩次,第一次查出了A集合,第二次卻不是了,因?yàn)槠渌聞?wù)插入了數(shù)據(jù),正好滿足這個(gè)事務(wù)的查詢條件。 |
注意事項(xiàng):
臟讀和不可重復(fù)讀的區(qū)別:臟讀讀到的臟數(shù)據(jù)是另一個(gè)事務(wù)沒有提交的數(shù)據(jù),但是不可重復(fù)讀讀到錯(cuò)誤數(shù)據(jù)是因?yàn)榱硪粋€(gè)事務(wù)把數(shù)據(jù)修改并提交了;幻讀和不可重復(fù)讀的區(qū)別:幻讀和不可重復(fù)讀都是讀到了另一個(gè)事務(wù)提交的數(shù)據(jù),但是不可重復(fù)讀是兩個(gè)事務(wù)針對(duì)同一個(gè)數(shù)據(jù)項(xiàng),而幻讀針對(duì)的是一個(gè)數(shù)據(jù)整體(數(shù)據(jù)條目)
為了解決上述提到的事務(wù)并發(fā)問題,數(shù)據(jù)庫(kù)提供一定的事務(wù)隔離機(jī)制來解決這個(gè)問題。
數(shù)據(jù)庫(kù)的事務(wù)隔離越嚴(yán)格,并發(fā)副作用越小,但付出的代價(jià)也就越大,因?yàn)槭聞?wù)隔離實(shí)質(zhì)上就是使用事務(wù)在一定程度上“串行化” 進(jìn)行,這顯然與“并發(fā)” 是矛盾的。
| RU:READ UNCOMMITED | 未提交讀(很少使用,基本沒有解決問題) | 丟失更新 |
| RC:READ COMMITED | 提交讀。顧名思義,保證一個(gè)事務(wù)只能看見另一個(gè)事務(wù)已經(jīng)提交的事務(wù)的結(jié)果。 | 丟失更新+臟讀 |
| RR:REPEATEABLE READ(Innodb默認(rèn)) | 可重復(fù)讀。顧名思義,解決了第三個(gè)并發(fā)問題:不可重復(fù)讀。 | 丟失更新+臟讀+不可重復(fù)讀 |
| S:SERIALIZABLE | 序列化。通過強(qiáng)制事務(wù)排序來讓他們串行執(zhí)行。(也很少使用)本質(zhì)上是給每個(gè)數(shù)據(jù)行都加上了共享鎖 | 四個(gè)問題都解決了 |
從上往下,隔離級(jí)別越來越高,但是代價(jià)肯定越來越大,真正選擇的時(shí)候需要斟酌,可以看到,要想真正解決幻讀問題,需要隔離級(jí)別為 S。
注意:
事實(shí)上 Mysql 的 InnoDB 通過 MVCC (Multi-Version Concurrent Control,多版本并發(fā)控制)機(jī)制解決了不可重復(fù)讀的基礎(chǔ)上,又解決了幻讀的的問題。
二、MySQL的鎖(結(jié)合 InnoDB引擎)
2.1 背景
對(duì)于數(shù)據(jù)庫(kù)事務(wù)的并發(fā)控制技術(shù)有很多,基于鎖、基于時(shí)間戳、基于MVCC的并發(fā)控制、基于MVCC的可串行化快照隔離等。
而我們討論的概念,是MySQL的事務(wù),再具體一些,是 InnoDB 支持的事務(wù)。
InnoDB?是支持?ACID?的,而MySQL用?InnoDB?作為自己的默認(rèn)存儲(chǔ)引擎,事務(wù)管理是?MySQL Server?實(shí)現(xiàn)框架和接口定義,而?InnoDB?提供具體的事務(wù)操作和并發(fā)控制,所以?MySQL 的事務(wù)模型,主要是指 MySQL 的InnoDB的事務(wù)管理部分。
InnoDB 使用鎖和 MVCC 技術(shù)來實(shí)現(xiàn)并發(fā)事務(wù)的訪問控制技術(shù)。
其中,鎖是并發(fā)控制的基礎(chǔ),在此基礎(chǔ)之上,實(shí)現(xiàn)了 MVCC 機(jī)制,用以提高基于鎖的方式帶來的低效率問題。
有了這個(gè)概念,我們下面分別討論?鎖?和?MVCC?兩個(gè)內(nèi)容。
MyISAM 默認(rèn)使用的是表鎖;
InnoDB 支持行級(jí)鎖,加上 MySQL Server 支持表級(jí)鎖,所以結(jié)合事務(wù)的時(shí)候,使用 InnoDB 引擎,系統(tǒng)就默認(rèn)使用的是行級(jí)鎖。
里我們討論的鎖的實(shí)現(xiàn),是為了事務(wù)的并發(fā)控制,所以都是在使用 InnoDB 引擎下的情況,那么有一些概念可能在其他引擎下的實(shí)現(xiàn)也是類似的。
2.2 鎖的分類
從對(duì)數(shù)據(jù)操作的粒度分 :
表鎖:操作時(shí),會(huì)鎖定整個(gè)表。(
MySQL Server)行鎖:操作時(shí),會(huì)鎖定當(dāng)前操作行。(也叫?
Record Lock,記錄鎖),實(shí)際上他是在索引上的記錄之鎖,因?yàn)?InnoDB 的表的組織結(jié)構(gòu)是通過 B+ 樹索引。
從對(duì)數(shù)據(jù)操作的類型分:
讀鎖(共享鎖、S鎖):針對(duì)同一份數(shù)據(jù),多個(gè)讀操作可以同時(shí)進(jìn)行而不會(huì)互相影響。
寫鎖(排它鎖、X鎖):當(dāng)前操作沒有完成之前,它會(huì)阻斷其他寫鎖和讀鎖。
意向共享鎖(IS):事務(wù)想要獲取一張表中某幾行的共享鎖
意向排它鎖(IX):事務(wù)想要獲取一張表中的某幾行的排它鎖
前兩個(gè)很容易理解,他們的兼容情況是:只有 S 鎖和 S 鎖是兼容的,其他的組合都是互斥的。
因?yàn)殒i的粒度不同,這就允許事務(wù)表級(jí)和行級(jí)的鎖可以同時(shí)存在,所以 InnoDB 支持了額外的一種鎖叫,意向鎖(Intention Lock):
問題:innodb的意向鎖有什么作用?
mysql官網(wǎng)上對(duì)于意向鎖的解釋中有這么一句話
“The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.”
意思是說加意向鎖的目的是為了表明某個(gè)事務(wù)正在鎖定一行或者將要鎖定一行。
那么,意向鎖的作用就是“表明”加鎖的意圖,可是為什么要表明這個(gè)意圖呢?
如果僅僅鎖定一行僅僅需要加一個(gè)鎖,那么就直接加鎖就好了,這里要表明加鎖意圖的原因是因?yàn)橐i定一行不僅僅是要加一個(gè)鎖,而是要做一系列操作嗎?
①在mysql中有表鎖,LOCK TABLE my_tabl_name READ; 用讀鎖鎖表,會(huì)阻塞其他事務(wù)修改表數(shù)據(jù)。LOCK
TABLE my_table_name WRITe; 用寫鎖鎖表,會(huì)阻塞其他事務(wù)讀和寫。
②Innodb引擎又支持行鎖,行鎖分為共享鎖,一個(gè)事務(wù)對(duì)一行的共享只讀鎖。排它鎖,一個(gè)事務(wù)對(duì)一行的排他讀寫鎖。
③這兩中類型的鎖共存的問題考慮這個(gè)例子:
事務(wù)A鎖住了表中的一行,讓這一行只能讀,不能寫。之后,事務(wù)B申請(qǐng)整個(gè)表的寫鎖。如果事務(wù)B申請(qǐng)成功,那么理論上它就能修改表中的任意一行,這與A持有的行鎖是沖突的。
數(shù)據(jù)庫(kù)需要避免這種沖突,就是說要讓B的申請(qǐng)被阻塞,直到A釋放了行鎖。
數(shù)據(jù)庫(kù)要怎么判斷這個(gè)沖突呢?
step1:判斷表是否已被其他事務(wù)用表鎖鎖表
step2:判斷表中的每一行是否已被行鎖鎖住。
注意step2,這樣的判斷方法效率實(shí)在不高,因?yàn)樾枰闅v整個(gè)表。
于是就有了意向鎖。在意向鎖存在的情況下,事務(wù)A必須先申請(qǐng)表的意向共享鎖,成功后再申請(qǐng)一行的行鎖。在意向鎖存在的情況下, 上面的判斷可以改成
step1:不變
step2:發(fā)現(xiàn)表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,因此,事務(wù)B申請(qǐng)表的寫鎖會(huì)被阻塞。
注意:申請(qǐng)意向鎖的動(dòng)作是數(shù)據(jù)庫(kù)完成的,就是說,事務(wù)A申請(qǐng)一行的行鎖的時(shí)候,數(shù)據(jù)庫(kù)會(huì)自動(dòng)先開始申請(qǐng)表的意向鎖,不需要我們程序員使用代碼來申請(qǐng)。
總結(jié):為了實(shí)現(xiàn)多粒度鎖機(jī)制(白話:為了表鎖和行鎖都能用)
那么這四個(gè)鎖在一起之后我們看看他們的兼容性:

2.3 其他鎖
InnoDB里還有幾個(gè)鎖:
間隙鎖(
Gap Locks):兩個(gè)索引項(xiàng)之間的間隔、稱為間隙,把這個(gè)間隙看作一個(gè)對(duì)象,在此對(duì)象上加鎖,就是間隙鎖。這個(gè)鎖是從加鎖的對(duì)象角度定義的鎖,所以和表、行是同一個(gè)角度的鎖。Next-Key鎖(
Next-Key Locks):行級(jí)鎖+間隙鎖共同組成。Insert Intention Locks:基于間隙鎖,專門用于 Insert 操作。
間隙鎖會(huì)在 RC 隔離級(jí)別的某些情況下使用,在 RR 隔離級(jí)別下,間隙鎖會(huì)和行級(jí)鎖合并成 Next-key 鎖使用。(記住這一點(diǎn))
2.4 鎖的施加細(xì)則
對(duì)于各種 MySQL 語句來說,InnoDB 對(duì)他們提供了事務(wù)操作的支持,這樣的支持就是通過并發(fā)控制的鎖來完成的。(當(dāng)然,還有補(bǔ)充的 MVCC 技術(shù),后面再說)
細(xì)則如下,(參考《數(shù)據(jù)庫(kù)事務(wù)處理的藝術(shù)》這本書里對(duì)官方文檔的總結(jié),只選擇了常用的命令):
* SELECT...FOR UPDATE 或 SELECT...LOCK IN SHARE MODE:首先對(duì)掃描過的行加鎖(實(shí)際上是對(duì)索引的記錄上加鎖),如果掃描過的行不滿足 WHERE 條件則釋放鎖(但是有時(shí)候,鎖的釋放不及時(shí)比如 UNION 操作下被掃描過的行可能會(huì)被放到臨時(shí)表里,那就直到查詢結(jié)束才會(huì)釋放鎖);
* ALTER TABLE ... LOCK [=]?{DEFAULT | NONE | SHARED | EXCLUSIVE}:在指定的表上施加讀鎖或者排他鎖;
* CREATE TABLE...SELECT...:其中的 SELECT 操作符合 SELECT 語句的枷鎖規(guī)則,只是不能帶有 FOR UPDATE 子句;
* DELETE FROM... WHERE...:在索引項(xiàng)上加排他的 Next-key 鎖;
* INSERT:在被插入的索引上加記錄鎖(意向鎖);
* INSERT... ON DUPLICATE KEY UPDATE:在被插入的索引項(xiàng)上加排他 Next-key 鎖;
* INSERT INTO T SELECT ... FROM S WHERE ...:對(duì)于被插入到 T 中的元組,在對(duì)應(yīng)的索引項(xiàng)上施加排他記錄鎖。如果隔離級(jí)別是 RC ,則在表 S 對(duì)應(yīng)的索引項(xiàng)上不加鎖,這是一個(gè)一致性讀操作;否則加上共享 Next-Key 鎖;
* SELECT ... LOCK IN SHARE MODE:在索引上施加共享 Next-Key 鎖;
* SELECT ... FROM ... FOR UPDATE:在索引項(xiàng)上施加排他 Next-Key 鎖,這樣的鎖會(huì)阻塞上一種”SELECT ... LOCK IN SHARE MODE“操作,但不會(huì)阻塞下一種?”SELECT ... FROM“?這樣的一致性讀操作;
* SELECT ... FROM 通常作為一個(gè)一致性讀操作,不需要加任何鎖。但是如果隔離級(jí)別是 S,那么也要在索引項(xiàng)上加對(duì)應(yīng)的共享 Next-Key 鎖;
* UPDATE ... WHERE ...:在索引項(xiàng)上施加排他的 Next-Key 鎖。
*?如果一個(gè)表上定義了外鍵約束,那么在出發(fā)約束條件被檢查的元組對(duì)應(yīng)的索引項(xiàng)上,任何操作都會(huì)施加共享Next-Key 鎖。
LOCK TABLES 命令是在表級(jí)鎖,這個(gè)實(shí)現(xiàn)是?MySQL Server層的實(shí)現(xiàn),InnoDB 則不會(huì)操作,那么如果 InnoDB 不知道 MySQL Server設(shè)置了表級(jí)鎖,就還可能出現(xiàn)一個(gè)死鎖問題,實(shí)踐中需要注意。
三、InnoDB 的MVCC原理
在基于鎖的并發(fā)控制的基礎(chǔ)之上,實(shí)現(xiàn)了 MVCC 技術(shù)。
首先還是強(qiáng)調(diào)一點(diǎn),MVCC 技術(shù)本身思想從名字就可以看的出來,就是通過多個(gè)版本進(jìn)行并發(fā)的控制。那么并發(fā)控制技術(shù),是需要配合其他的并發(fā)控制技術(shù)來具體實(shí)現(xiàn),這里我們講的 InnoDB 的 MVCC 原理,就是基于鎖的。
3.1 日志
日志是保證事務(wù)的原子性、持久性的重要技術(shù)之一,在?MVCC?的實(shí)現(xiàn)中也是要用到的,這里簡(jiǎn)單介紹一下,對(duì)于每一個(gè) SQL 操作,都不是一下子執(zhí)行完成,因此數(shù)據(jù)的狀態(tài)都要變化,那么把這個(gè)過程記錄下來,出現(xiàn)問題進(jìn)行”回放“,就能應(yīng)對(duì)事物的原子性和持久性。
需要記錄的數(shù)據(jù)通常包括:
事務(wù)標(biāo)識(shí):比如事務(wù) id;
數(shù)據(jù)項(xiàng)的標(biāo)識(shí);
舊值:數(shù)據(jù)項(xiàng)被修改之前的值,又稱為 前像;
新值:數(shù)據(jù)項(xiàng)被修改之后的值,又稱為后像。
一系列的 SQL 操作過程變成一個(gè)序列,這就是日志,數(shù)據(jù)庫(kù)引擎在具體實(shí)現(xiàn)的時(shí)候會(huì)把日志放到日志緩存區(qū),然后刷出到外存,存放到日志文件。
日志文件一般分為?REDO?日志和?UNDO?日志:
REDO 日志記錄事務(wù)的標(biāo)識(shí)、數(shù)據(jù)項(xiàng)的標(biāo)識(shí)和 新值;UNDO 日志記錄事務(wù)的標(biāo)識(shí)、數(shù)據(jù)項(xiàng)的標(biāo)識(shí)和 舊值。(InnoDB 的 MVCC 用到的是 UNDO log)
3.2 InnoDB 的MVCC
因?yàn)?InnoDB?的多版本,指的是?行(元組)?級(jí)別的版本,在每行(或者每個(gè)元組、每條記錄)上,都有一些和并發(fā)、回滾相關(guān)的隱含字段,分別為:
DB_TRX_ID:很好理解,就是 id,表示上一個(gè)執(zhí)行(insert | update)操作的事務(wù)。至于delete操作,InnoDB 認(rèn)為是一個(gè) update 操作,不過會(huì)更新一個(gè)另外的刪除位,將行表示為deleted。并非真正刪除。DB_ROLL_PTR:就是?pointer,回滾指針,指向的就是一個(gè)舊版本。那么其實(shí)指向的是當(dāng)前記錄行的 undo log 信息,是舊版本的數(shù)據(jù)位于回滾段中的位置,通過這個(gè)指針能夠找到舊版本;DB_ROW_ID:隨著新行插入而單調(diào)遞增的行 ID,和 MVCC 關(guān)系不大。
在回滾段里的 UNDO 日志分為兩種:
INSERT UNDO logs:插入到回滾段中的日志,僅用于事務(wù)提交時(shí)使用,當(dāng)事務(wù)提交,則插入 UNDO 日志里的內(nèi)容被清除;UPDATE UNDO logs:被用于一致性無鎖讀,為一致性讀提供快照隔離下的可被讀取的老版本數(shù)據(jù)。當(dāng)沒有需要滿足一致性讀的快照時(shí),一些老版本數(shù)據(jù)才能被清理。
以上,實(shí)現(xiàn)的原理基本告一段落,但是 InnoDB 的實(shí)現(xiàn)層面,還有另一個(gè)數(shù)據(jù)結(jié)構(gòu),就是?Read View?快照。
Read View(讀視圖),跟快照、snapshot 是一個(gè)概念,可以看作事務(wù)的生命周期里面的一段,而不同的快照就是不同的段。在源碼層面,他是一個(gè)類,名叫 ReadView,這里面的內(nèi)容,重點(diǎn)有兩個(gè):一個(gè)就是保存了快照的左右邊界
另一個(gè)是提供了如何判斷當(dāng)前行(元組)的可見性的標(biāo)志。
3.3 和 MVCC 有關(guān)的額外兩個(gè)概念
快照讀(snapshot read):普通的 select 語句(不包括 select ... lock in share mode, select ... for update)。也就是不加鎖的非阻塞讀,所以在串行級(jí)別下的快照讀會(huì)退化成當(dāng)前讀。他是基于多版本的,那么快照讀可能讀到的并不一定是數(shù)據(jù)的最新版本,而有可能是之前的歷史版本。當(dāng)前讀(current read)?:select ... lock in share mode,select ... for update,insert,update,delete 語句。(為什么叫當(dāng)前讀?就是它讀取的是記錄的最新版本,讀取時(shí)還要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,會(huì)對(duì)讀取的記錄進(jìn)行加鎖。)
對(duì)于 InnoDB 的 MVCC
實(shí)現(xiàn),很多博客和書都是架空寫的原理,看了《高性能MySQL》,里面也寫的是基于每個(gè)事務(wù)操作的時(shí)候給該行添加兩個(gè)版本號(hào)(當(dāng)前版本號(hào)、刪除版本號(hào)),然后事務(wù)操作的時(shí)候根據(jù)版本號(hào)來判定是否執(zhí)行完畢還是回滾等規(guī)則?,F(xiàn)在看來并不是這樣,但是從上面提到的、源碼里實(shí)際增加的字段來看,思想是大概類似的,不過實(shí)現(xiàn)的機(jī)制更加復(fù)雜。
四、MVCC 原理總結(jié)
4.1 總結(jié) MVCC 原理
到這一步,概念有點(diǎn)多,我們來梳理一下。
首先,事務(wù)的概念有了,事務(wù)的特性隨著概念出來:
ACID。那么,并發(fā)事務(wù)如果不加控制,就會(huì)存在問題,按處理的難易程度從低到高:丟失更新-臟讀-不可重復(fù)讀-幻讀。
于是,數(shù)據(jù)庫(kù)如果實(shí)現(xiàn)事務(wù),就要保證特性,解決對(duì)應(yīng)的問題,應(yīng)運(yùn)而生四個(gè)隔離級(jí)別:
RU-RC-RR-S。因?yàn)槭聞?wù)是在存儲(chǔ)引擎層實(shí)現(xiàn)的,所以接下來討論了基于?InnoDB 引擎的 MySQL 事務(wù)的實(shí)現(xiàn):
額外的幾個(gè)字段;
基于?
Undo log;結(jié)合?
Read View?(快照)。相關(guān)概念:鎖的分類:表鎖、行鎖。讀鎖、寫鎖、意向鎖。間隙鎖(Gap Locks)、Next-Key鎖;
相關(guān)概念:鎖的實(shí)施細(xì)則;
相關(guān)概念:基于鎖的并發(fā)版本控制,結(jié)合 MVCC。如果沒有 MVCC ,四個(gè)并發(fā)問題,除了讀讀不用加鎖,讀寫、寫讀、寫寫都不能并發(fā)執(zhí)行,否則就會(huì)產(chǎn)生問題,效率低下,有了MVCC,讀寫和寫讀可以并發(fā)執(zhí)行。那 MVCC 都用到了什么呢?
現(xiàn)在我們回過頭看一下MVCC機(jī)制工作的兩個(gè)隔離級(jí)別:RC 和 RR :
RC,提交讀,要解決臟讀的問題,保證一個(gè)事務(wù)讀取到的一定是另一個(gè)事務(wù)的已經(jīng)提交的結(jié)果,而不能是未提交的結(jié)果。那么,對(duì)于 RC級(jí)別來說,務(wù)中,每次快照讀都會(huì)新生成一個(gè)快照和Read View,保證每個(gè)事務(wù)可以看到別的事務(wù)提交的更新。(關(guān)于具體的實(shí)現(xiàn)還有相應(yīng)的算法,可見性之類的);RR,可重復(fù)讀,要解決不可重復(fù)讀的問題,保證快照讀生成Read View時(shí),Read View會(huì)記錄此時(shí)所有其他活動(dòng)事務(wù)的快照,這些事務(wù)的修改對(duì)于當(dāng)前事務(wù)都是不可見的。
4.2 MVCC 解決幻讀問題了嗎?
在?RR?級(jí)別下,沒有完全解決幻讀?的問題。
我們回憶幻讀的概念:一個(gè)事務(wù)按照相同的查詢條件查兩次,第一次查出了A集合,第二次卻不是了,因?yàn)槠渌聞?wù)插入了數(shù)據(jù),正好滿足這個(gè)事務(wù)的查詢條件。
那么對(duì)于上面的 MVCC 原理,基于快照讀的情況:
事務(wù) A 開始后,執(zhí)行普通 select 語句,創(chuàng)建了快照;
事務(wù) B 執(zhí)行 insert 語句;
事務(wù) A 再執(zhí)行普通 select 語句,得到的還是之前 B 沒有 insert 過的數(shù)據(jù),因?yàn)檫@時(shí)候 A 讀的數(shù)據(jù)是符合快照可見性條件的數(shù)據(jù)。
這是解決了幻讀問題的。
但是考慮這種情況:
事務(wù)A執(zhí)行的不是普通 select 語句,而是 select ... for update 等語句,根據(jù)上面的定義,事務(wù) A 是當(dāng)前讀,每次語句執(zhí)行的時(shí)候都是獲取的最新數(shù)據(jù)。
那么 B 執(zhí)行 insert語句;
A 再次查詢的時(shí)候,就可能會(huì)查到多一條數(shù)據(jù),產(chǎn)生幻讀。
這個(gè)時(shí)候就要出場(chǎng)我們?cè)谇懊娴逆i分類部分的一行紅字,另外兩個(gè)鎖:間隙鎖和 Next-key Locks。
間隙鎖在 RR 級(jí)別發(fā)揮作用,結(jié)合行級(jí)鎖稱為 Next-key locks,解決幻讀問題。具體的算法可能很復(fù)雜,原理就是鎖定范圍的設(shè)置加上了間隙,這樣插入操作肯定是沒辦法進(jìn)行的,因此就不會(huì)存在其他事務(wù)的插入操作導(dǎo)致幻讀了。
到這里我們可以得出結(jié)論,MySQL 里完全解決幻讀的方法有兩個(gè):
直接使用 S 隔離等級(jí)完全串行化;
RR 的隔離級(jí)別結(jié)合 MVCC 機(jī)制,還要結(jié)合 間隙鎖。
五、其他

關(guān)于數(shù)據(jù)庫(kù)的鎖,服務(wù)器層面的實(shí)現(xiàn)默認(rèn)是有表級(jí)別的鎖,沒有行鎖(書上沒有提這個(gè)點(diǎn),但是網(wǎng)上也有說法講服務(wù)器層面也實(shí)現(xiàn)了行鎖);
不同的存儲(chǔ)引擎層面又以自己的方式實(shí)現(xiàn)了不同粒度級(jí)別的鎖,因此選擇引擎不同的時(shí)候,我們使用的鎖,了解的原理都是基于引擎的,另一方面,鎖總和事務(wù)聯(lián)系在一起討論,不同的存儲(chǔ)引擎是否支持事務(wù)又是不一樣的,所以應(yīng)該是把server層關(guān)于這一塊忽略掉了。


??? ?
感謝點(diǎn)贊支持下哈?
