一、MVCC簡介
MVCC (Multiversion Concurrency Control)芽突,即多版本并發(fā)控制技術(shù),它使得大部分支持行鎖的事務(wù)引擎疾党,不再單純的使用行鎖來進行數(shù)據(jù)庫的并發(fā)控制,取而代之的是把數(shù)據(jù)庫的行鎖與行的多個版本結(jié)合起來猛铅,只需要很小的開銷,就可以實現(xiàn)非鎖定讀陆蟆,從而大大提高數(shù)據(jù)庫系統(tǒng)的并發(fā)性能
讀鎖:也叫共享鎖雷厂、S鎖,若事務(wù)T對數(shù)據(jù)對象A加上S鎖叠殷,則事務(wù)T可以讀A但不能修改A改鲫,其他事務(wù)只能再對A加S鎖,而不能加X鎖林束,直到T釋放A上的S 鎖像棘。這保證了其他事務(wù)可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改诊县。
寫鎖:又稱排他鎖讲弄、X鎖措左。若事務(wù)T對數(shù)據(jù)對象A加上X鎖依痊,事務(wù)T可以讀A也可以修改A,其他事務(wù)不能再對A加任何鎖怎披,直到T釋放A上的鎖胸嘁。這保證了其他事務(wù)在T釋放A上的鎖之前不能再讀取和修改A。
表鎖:操作對象是數(shù)據(jù)表凉逛。Mysql大多數(shù)鎖策略都支持(常見mysql innodb)性宏,是系統(tǒng)開銷最低但并發(fā)性最低的一個鎖策略。事務(wù)t對整個表加讀鎖状飞,則其他事務(wù)可讀不可寫毫胜,若加寫鎖,則其他事務(wù)增刪改都不行诬辈。
行級鎖:操作對象是數(shù)據(jù)表中的一行酵使。是MVCC技術(shù)用的比較多的,但在MYISAM用不了焙糟,行級鎖用mysql的儲存引擎實現(xiàn)而不是mysql服務(wù)器口渔。但行級鎖對系統(tǒng)開銷較大,處理高并發(fā)較好穿撮。
二缺脉、MVCC實現(xiàn)原理
innodb MVCC主要是為Repeatable-Read事務(wù)隔離級別做的痪欲。在此隔離級別下,A攻礼、B客戶端所示的數(shù)據(jù)相互隔離业踢,互相更新不可見
了解innodb的行結(jié)構(gòu)、Read-View的結(jié)構(gòu)對于理解innodb mvcc的實現(xiàn)由重要意義
innodb存儲的最基本row中包含一些額外的存儲信息 DATA_TRX_ID礁扮,DATA_ROLL_PTR陨亡,DB_ROW_ID,DELETE BIT
6字節(jié)的DATA_TRX_ID 標(biāo)記了最新更新這條行記錄的transaction id深员,每處理一個事務(wù)负蠕,其值自動+1
7字節(jié)的DATA_ROLL_PTR 指向當(dāng)前記錄項的rollback segment的undo log記錄,找之前版本的數(shù)據(jù)就是通過這個指針
6字節(jié)的DB_ROW_ID倦畅,當(dāng)由innodb自動產(chǎn)生聚集索引時遮糖,聚集索引包括這個DB_ROW_ID的值,否則聚集索引中不包括這個值.叠赐,這個用于索引當(dāng)中
DELETE BIT位用于標(biāo)識該記錄是否被刪除欲账,這里的不是真正的刪除數(shù)據(jù),而是標(biāo)志出來的刪除芭概。真正意義的刪除是在commit的時候
具體的執(zhí)行過程
begin->用排他鎖鎖定該行->記錄redo log->記錄undo log->修改當(dāng)前行的值赛不,寫事務(wù)編號,回滾指針指向undo log中的修改前的行
上述過程確切地說是描述了UPDATE的事務(wù)過程罢洲,其實undo log分insert和update undo log踢故,因為insert時,原始的數(shù)據(jù)并不存在惹苗,所以回滾時把insert undo log丟棄即可殿较,而update undo log則必須遵守上述過程
下面分別以select、delete桩蓉、 insert淋纲、 update語句來說明
SELECT
Innodb檢查每行數(shù)據(jù),確保他們符合兩個標(biāo)準:
1院究、InnoDB只查找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(也就是數(shù)據(jù)行的版本必須小于等于事務(wù)的版本)洽瞬,這確保當(dāng)前事務(wù)讀取的行都是事務(wù)之前已經(jīng)存在的,或者是由當(dāng)前事務(wù)創(chuàng)建或修改的行
2业汰、行的刪除操作的版本一定是未定義的或者大于當(dāng)前事務(wù)的版本號伙窃,確定了當(dāng)前事務(wù)開始之前,行沒有被刪除
符合了以上兩點則返回查詢結(jié)果蔬胯。
INSERT
InnoDB為每個新增行記錄當(dāng)前系統(tǒng)版本號作為創(chuàng)建ID对供。
DELETE
InnoDB為每個刪除行的記錄當(dāng)前系統(tǒng)版本號作為行的刪除ID。
UPDATE
InnoDB復(fù)制了一行。這個新行的版本號使用了系統(tǒng)版本號产场。它也把系統(tǒng)版本號作為了刪除行的版本鹅髓。
說明
insert操作時 “創(chuàng)建時間”=DB_ROW_ID,這時京景,“刪除時間 ”是未定義的窿冯;
update時,復(fù)制新增行的“創(chuàng)建時間”=DB_ROW_ID确徙,刪除時間未定義醒串,舊數(shù)據(jù)行“創(chuàng)建時間”不變,刪除時間=該事務(wù)的DB_ROW_ID鄙皇;
delete操作芜赌,相應(yīng)數(shù)據(jù)行的“創(chuàng)建時間”不變,刪除時間=該事務(wù)的DB_ROW_ID伴逸;
select操作對兩者都不修改缠沈,只讀相應(yīng)的數(shù)據(jù)
三、對于MVCC的總結(jié)
上述更新前建立undo log错蝴,根據(jù)各種策略讀取時非阻塞就是MVCC洲愤,undo log中的行就是MVCC中的多版本,這個可能與我們所理解的MVCC有較大的出入顷锰,一般我們認為MVCC有下面幾個特點:
- 每行數(shù)據(jù)都存在一個版本柬赐,每次數(shù)據(jù)更新時都更新該版本
- 修改時Copy出當(dāng)前版本隨意修改,各個事務(wù)之間無干擾
- 保存時比較版本號官紫,如果成功(commit)肛宋,則覆蓋原記錄;失敗則放棄copy(rollback)
就是每行都有版本號万矾,保存時根據(jù)版本號決定是否成功悼吱,聽起來含有樂觀鎖的味道慎框,而Innodb的實現(xiàn)方式是:
- 事務(wù)以排他鎖的形式修改原始數(shù)據(jù)
- 把修改前的數(shù)據(jù)存放于undo log良狈,通過回滾指針與主數(shù)據(jù)關(guān)聯(lián)
- 修改成功(commit)啥都不做,失敗則恢復(fù)undo log中的數(shù)據(jù)(rollback)
二者最本質(zhì)的區(qū)別是笨枯,當(dāng)修改數(shù)據(jù)時是否要排他鎖定薪丁,如果鎖定了還算不算是MVCC?
Innodb的實現(xiàn)真算不上MVCC馅精,因為并沒有實現(xiàn)核心的多版本共存严嗜,undo log中的內(nèi)容只是串行化的結(jié)果,記錄了多個事務(wù)的過程洲敢,不屬于多版本共存漫玄。但理想的MVCC是難以實現(xiàn)的,當(dāng)事務(wù)僅修改一行記錄使用理想的MVCC模式是沒有問題的,可以通過比較版本號進行回滾睦优;但當(dāng)事務(wù)影響到多行數(shù)據(jù)時渗常,理想的MVCC據(jù)無能為力了。
比如汗盘,如果Transaciton1執(zhí)行理想的MVCC皱碘,修改Row1成功,而修改Row2失敗隐孽,此時需要回滾Row1癌椿,但因為Row1沒有被鎖定,其數(shù)據(jù)可能又被Transaction2所修改菱阵,如果此時回滾Row1的內(nèi)容踢俄,則會破壞Transaction2的修改結(jié)果,導(dǎo)致Transaction2違反ACID晴及。
理想MVCC難以實現(xiàn)的根本原因在于企圖通過樂觀鎖代替二段提交褪贵。修改兩行數(shù)據(jù),但為了保證其一致性抗俄,與修改兩個分布式系統(tǒng)中的數(shù)據(jù)并無區(qū)別脆丁,而二提交是目前這種場景保證一致性的唯一手段。二段提交的本質(zhì)是鎖定动雹,樂觀鎖的本質(zhì)是消除鎖定槽卫,二者矛盾,故理想的MVCC難以真正在實際中被應(yīng)用胰蝠,Innodb只是借了MVCC這個名字歼培,提供了讀的非阻塞而已。
參考文章
https://www.percona.com/blog/2014/12/17/innodbs-multi-versioning-handling-can-be-achilles-heel/
http://www.xdata.me/?p=289
http://blogread.cn/it/article/5969
http://blog.csdn.net/chen77716/article/details/6742128
http://blog.chinaunix.net/link.php?url=http://forge.mysql.com%2Fwiki%2FMySQL_Internals