一磨确、事務(wù)(數(shù)據(jù)庫的事務(wù)都通用的定義)
1.1 事務(wù)定義
事務(wù)是由一步或幾步數(shù)據(jù)庫操作序列組成邏輯執(zhí)行單元沽甥,這系列操作要么全部執(zhí)行,要么全部放棄執(zhí)行乏奥。事務(wù)通常以?BEGIN TRANSACTION?開始摆舟,以COMMIT?或?ROLLBACK?操作結(jié)束:
COMMIT?即提交,提交事務(wù)中所有的操作邓了、事務(wù)正常結(jié)束恨诱;
ROLLBACK?即回滾,撤銷已做的所有操作驶悟,回滾到事務(wù)開始的狀態(tài)胡野。
1.2 事務(wù)的四種特性
ACID:原子性,一致性痕鳍,隔離性硫豆,持久性。
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ù)庫的事務(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ù)庫提供一定的事務(wù)隔離機(jī)制來解決這個(gè)問題墩瞳。
數(shù)據(jù)庫的事務(wù)隔離越嚴(yán)格,并發(fā)副作用越小氏豌,但付出的代價(jià)也就越大喉酌,因?yàn)槭聞?wù)隔離實(shí)質(zhì)上就是使用事務(wù)在一定程度上“串行化” 進(jìn)行,這顯然與“并發(fā)” 是矛盾的泵喘。
隔離級(jí)別中文事務(wù)解決問題
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ù)庫事務(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ù)庫需要避免這種沖突燥爷,就是說要讓B的申請(qǐng)被阻塞,直到A釋放了行鎖懦窘。
數(shù)據(jù)庫要怎么判斷這個(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ù)庫完成的逻住,就是說施流,事務(wù)A申請(qǐng)一行的行鎖的時(shí)候,數(shù)據(jù)庫會(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ù)庫事務(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)中也是要用到的,這里簡單介紹一下辅肾,對(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ù)庫引擎在具體實(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ī)則。現(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ù)庫如果實(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ù)庫的鎖钦椭,服務(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)于這一塊忽略掉了。