- 在SQL標(biāo)準(zhǔn)中,RR是無(wú)法避免幻讀問(wèn)題的鸣驱,但是InnoDB實(shí)現(xiàn)的RR避免了幻讀問(wèn)題泛鸟。
- RR解決臟讀、不可重復(fù)讀踊东、幻讀等問(wèn)題北滥,使用的是MVCC:MVCC全稱Multi-Version Concurrency Control,即多版本的并發(fā)控制協(xié)議递胧。
- 多版本并發(fā)控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存儲(chǔ)引擎實(shí)現(xiàn)隔離級(jí)別的一種具體方式碑韵,用于實(shí)現(xiàn)提交讀和可重復(fù)讀這兩種隔離級(jí)別。
基本思想
加鎖能解決多個(gè)事務(wù)同時(shí)執(zhí)行時(shí)出現(xiàn)的并發(fā)一致性問(wèn)題缎脾。在實(shí)際場(chǎng)景中讀操作往往多于寫(xiě)操作祝闻,因此又引入了讀寫(xiě)鎖來(lái)避免不必要的加鎖操作,例如讀和讀沒(méi)有互斥關(guān)系遗菠。讀寫(xiě)鎖中讀和寫(xiě)操作仍然是互斥的联喘,而 MVCC 利用了多版本的思想,寫(xiě)操作更新最新的版本快照辙纬,而讀操作去讀舊版本快照豁遭,沒(méi)有互斥關(guān)系,這一點(diǎn)和 CopyOnWrite 類似贺拣。
在 MVCC 中事務(wù)的修改操作(DELETE蓖谢、INSERT、UPDATE)會(huì)為數(shù)據(jù)行新增一個(gè)版本快照譬涡。
臟讀和不可重復(fù)讀最根本的原因是事務(wù)讀取到其它事務(wù)未提交的修改闪幽。在事務(wù)進(jìn)行讀取操作時(shí),為了解決臟讀和不可重復(fù)讀問(wèn)題涡匀,MVCC 規(guī)定只能讀取已經(jīng)提交的快照盯腌。當(dāng)然一個(gè)事務(wù)可以讀取自身未提交的快照,這不算是臟讀陨瘩。
特點(diǎn):在同一時(shí)刻腕够,不同事務(wù)讀取到的數(shù)據(jù)可能是不同的(即多版本)
實(shí)現(xiàn):通過(guò)保存數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)的快照來(lái)實(shí)現(xiàn)
版本號(hào)
- 系統(tǒng)版本號(hào)SYS_ID:遞增數(shù)字,每開(kāi)始一個(gè)新的事務(wù)舌劳,系統(tǒng)版本號(hào)就會(huì)自動(dòng)遞增帚湘。
- 事務(wù)版本號(hào)TRX_ID:事務(wù)開(kāi)始時(shí)的系統(tǒng)版本號(hào)。
Undo日志
MVCC 的多版本指的是多個(gè)版本的快照甚淡,快照存儲(chǔ)在 Undo 日志中客们,該日志通過(guò)回滾指針 ROLL_PTR 把一個(gè)數(shù)據(jù)行的所有快照連接起來(lái)。
例如在 MySQL 創(chuàng)建一個(gè)表 t,包含主鍵 id 和一個(gè)字段 x底挫。我們先插入一個(gè)數(shù)據(jù)行恒傻,然后對(duì)該數(shù)據(jù)行執(zhí)行兩次更新操作导帝。
INSERT INTO t(id, x) VALUES(1, "a");
UPDATE t SET x="b" WHERE id=1;
UPDATE t SET x="c" WHERE id=1;
因?yàn)闆](méi)有使用 START TRANSACTION
將上面的操作當(dāng)成一個(gè)事務(wù)來(lái)執(zhí)行既穆,根據(jù) MySQL 的 AUTOCOMMIT 機(jī)制岩灭,每個(gè)操作都會(huì)被當(dāng)成一個(gè)事務(wù)來(lái)執(zhí)行椭住,所以上面的操作總共涉及到三個(gè)事務(wù)客燕》锞蓿快照中除了記錄事務(wù)版本號(hào) TRX_ID 和操作之外内边,還記錄了一個(gè) bit 的 DEL 字段票灰,用于標(biāo)記是否被刪除注簿。
INSERT契吉、UPDATE、DELETE 操作會(huì)創(chuàng)建一個(gè)日志诡渴,并將事務(wù)版本號(hào) TRX_ID 寫(xiě)入捐晶。DELETE 可以看成是一個(gè)特殊的 UPDATE,還會(huì)額外將 DEL 字段設(shè)置為 1妄辩。
InnoDB實(shí)現(xiàn)MVCC
MVCC最大的優(yōu)點(diǎn)是讀不加鎖惑灵,因此讀寫(xiě)不沖突,并發(fā)性能好眼耀。InnoDB實(shí)現(xiàn)MVCC英支,多個(gè)版本的數(shù)據(jù)可以共存,主要基于以下技術(shù)及數(shù)據(jù)結(jié)構(gòu):
1)隱藏列:InnoDB中每行數(shù)據(jù)都有隱藏列哮伟,隱藏列中包含了本行數(shù)據(jù)的事務(wù)id干花、指向undo log的指針等。
2)基于undo log的版本鏈:前面說(shuō)到每行數(shù)據(jù)的隱藏列中包含了指向undo log的指針楞黄,而每條undo log也會(huì)指向更早版本的undo log池凄,從而形成一條版本鏈。
3)ReadView:通過(guò)隱藏列和版本鏈谅辣,MySQL可以將數(shù)據(jù)恢復(fù)到指定版本;但是具體要恢復(fù)到哪個(gè)版本婶恼,則需要根據(jù)ReadView來(lái)確定桑阶。所謂ReadView勾邦,是指事務(wù)(記做事務(wù)A)在某一時(shí)刻給整個(gè)事務(wù)系統(tǒng)(trx_sys)打快照蚣录,之后再進(jìn)行讀操作時(shí),會(huì)將讀取到的數(shù)據(jù)中的事務(wù)id與trx_sys快照比較眷篇,從而判斷數(shù)據(jù)對(duì)該ReadView是否可見(jiàn)萎河,即對(duì)事務(wù)A是否可見(jiàn)。
trx_sys中的主要內(nèi)容,以及判斷可見(jiàn)性的方法如下:
- low_limit_id:表示生成ReadView時(shí)系統(tǒng)中應(yīng)該分配給下一個(gè)事務(wù)的id虐杯。如果數(shù)據(jù)的事務(wù)id大于等于low_limit_id玛歌,則對(duì)該ReadView不可見(jiàn)。
- up_limit_id:表示生成ReadView時(shí)當(dāng)前系統(tǒng)中活躍的讀寫(xiě)事務(wù)中最小的事務(wù)id擎椰。如果數(shù)據(jù)的事務(wù)id小于up_limit_id支子,則對(duì)該ReadView可見(jiàn)。
- rw_trx_ids:表示生成ReadView時(shí)當(dāng)前系統(tǒng)中活躍的讀寫(xiě)事務(wù)的事務(wù)id列表达舒。如果數(shù)據(jù)的事務(wù)id在low_limit_id和up_limit_id之間值朋,則需要判斷事務(wù)id是否在rw_trx_ids中:如果在,說(shuō)明生成ReadView時(shí)事務(wù)仍在活躍中巩搏,因此數(shù)據(jù)對(duì)ReadView不可見(jiàn)昨登;如果不在,說(shuō)明生成ReadView時(shí)事務(wù)已經(jīng)提交了贯底,因此數(shù)據(jù)對(duì)ReadView可見(jiàn)丰辣。
擴(kuò)展
前面介紹的MVCC,是RR隔離級(jí)別下“非加鎖讀”實(shí)現(xiàn)隔離性的方式丈甸。下面是一些簡(jiǎn)單的擴(kuò)展糯俗。
(1)讀已提交(RC)隔離級(jí)別下的非加鎖讀
RC與RR一樣,都使用了MVCC睦擂,其主要區(qū)別在于:
RR是在事務(wù)開(kāi)始后第一次執(zhí)行select前創(chuàng)建ReadView得湘,直到事務(wù)提交都不會(huì)再創(chuàng)建。根據(jù)前面的介紹顿仇,RR可以避免臟讀淘正、不可重復(fù)讀和幻讀。
RC每次執(zhí)行select前都會(huì)重新建立一個(gè)新的ReadView臼闻,因此如果事務(wù)A第一次select之后鸿吆,事務(wù)B對(duì)數(shù)據(jù)進(jìn)行了修改并提交,那么事務(wù)A第二次select時(shí)會(huì)重新建立新的ReadView述呐,因此事務(wù)B的修改對(duì)事務(wù)A是可見(jiàn)的惩淳。因此RC隔離級(jí)別可以避免臟讀,但是無(wú)法避免不可重復(fù)讀和幻讀乓搬。
(2)加鎖讀與next-key lock
按照是否加鎖思犁,MySQL的讀可以分為兩種:
一種是非加鎖讀,也稱作快照讀进肯、一致性讀激蹲,使用普通的select語(yǔ)句,這種情況下使用MVCC避免了臟讀江掩、不可重復(fù)讀学辱、幻讀乘瓤,保證了隔離性。
另一種是加鎖讀策泣,查詢語(yǔ)句有所不同衙傀,如下所示:
#共享鎖讀取
select...lock in share mode
#排它鎖讀取
select...for update
加鎖讀在查詢時(shí)會(huì)對(duì)查詢的數(shù)據(jù)加鎖(共享鎖或排它鎖)。由于鎖的特性着降,當(dāng)某事務(wù)對(duì)數(shù)據(jù)進(jìn)行加鎖讀后差油,其他事務(wù)無(wú)法對(duì)數(shù)據(jù)進(jìn)行寫(xiě)操作,因此可以避免臟讀和不可重復(fù)讀任洞。而避免幻讀蓄喇,則需要通過(guò)next-key lock。next-key lock是行鎖的一種交掏,實(shí)現(xiàn)相當(dāng)于record lock(記錄鎖) + gap lock(間隙鎖)妆偏;其特點(diǎn)是不僅會(huì)鎖住記錄本身(record lock的功能),還會(huì)鎖定一個(gè)范圍(gap lock的功能)盅弛。因此钱骂,加鎖讀同樣可以避免臟讀、不可重復(fù)讀和幻讀挪鹏,保證隔離性见秽。
總結(jié)
概括來(lái)說(shuō),InnoDB實(shí)現(xiàn)的RR讨盒,通過(guò)鎖機(jī)制(包含next-key lock)解取、MVCC(包括數(shù)據(jù)的隱藏列、基于undo log的版本鏈返顺、ReadView)等禀苦,實(shí)現(xiàn)了一定程度的隔離性,可以滿足大多數(shù)場(chǎng)景的需要遂鹊。
不過(guò)需要說(shuō)明的是振乏,RR雖然避免了幻讀問(wèn)題,但是畢竟不是Serializable秉扑,不能保證完全的隔離慧邮,下面是一個(gè)例子:
- 如果在事務(wù)中第一次讀取采用非加鎖讀,第二次讀取采用加鎖讀舟陆,則如果在兩次讀取之間數(shù)據(jù)發(fā)生了變化误澳,兩次讀取到的結(jié)果不一樣,因?yàn)榧渔i讀時(shí)不會(huì)采用MVCC吨娜。