mysql innodb能高效運(yùn)行,支撐高并發(fā)原因就是基于MVCC實(shí)現(xiàn)。
本文僅是簡(jiǎn)單介紹下MVCC原理昧诱,介紹事務(wù)隔離級(jí)別RC,RR中原理所袁。
MVCC
MVCC(全稱Multi-Version Concurrency Control)鳄哭,多版本并發(fā)控制,解決數(shù)據(jù)庫(kù)的并發(fā)訪問(wèn)高效問(wèn)題纲熏。
維基百科中有更具體的描述,感興趣可以看看妆丘。
MVCC到底解決什么問(wèn)題
數(shù)據(jù)庫(kù)中,讀數(shù)據(jù)的同時(shí)局劲,有另外的任務(wù)寫入數(shù)據(jù)勺拣,在沒(méi)有任何鎖機(jī)制情況下,很有可能讀到是『半寫』或者不一致的數(shù)據(jù)鱼填。
當(dāng)然解決這個(gè)問(wèn)題最簡(jiǎn)單的方法药有,通過(guò)加鎖,即Share lock,write lock,會(huì)有讀讀兼容,讀寫或?qū)懽x阻塞問(wèn)題愤惰,這種方式效率很低苇经。實(shí)際中,在寫某行數(shù)據(jù)同時(shí)宦言,又另外任務(wù)使用mysql同時(shí)去讀數(shù)據(jù)扇单,讀操作并沒(méi)有感受到block,反而覺(jué)得效率很高奠旺≈├剑可以確定是mysql中沒(méi)有最簡(jiǎn)單的機(jī)制來(lái)保證并發(fā),官方文檔上申明使用了MVCC响疚。
當(dāng)然鄙信,innodb是行鎖,針對(duì)記錄的更新會(huì)加排他鎖忿晕,保證并發(fā)時(shí)資源時(shí)線程安全装诡。
鎖的介紹博文中有介紹,這里就不多介紹践盼。
MVCC是怎么做到的
mysql文檔:InnoDB是一個(gè)多版本的存儲(chǔ)引擎慎王,保存有關(guān)行已更改的old version的信息,以支持并發(fā)和回滾等事務(wù)功能宏侍。
簡(jiǎn)而言之:
MVCC使用了快照手段,每個(gè)連接到數(shù)據(jù)庫(kù)的讀者蜀漆,在某個(gè)瞬間看到的是數(shù)據(jù)庫(kù)的 某一個(gè)快照谅河,寫者寫操作造成的變化在寫操作完成之前(或者數(shù)據(jù)庫(kù)事務(wù)提交之前)對(duì)于其他的讀者來(lái)說(shuō)是不可見(jiàn)的。
內(nèi)部确丢,InnoDB為數(shù)據(jù)庫(kù)中存儲(chǔ)的每一行j記錄添加三個(gè)字段:
- 6字節(jié)的事務(wù)ID(DB_TRX_ID ):表示插入或更新該行的最后一個(gè)事務(wù)的事務(wù)標(biāo)識(shí)符
- 7字節(jié)的回滾指針(DB_ROLL_PTR):指向?qū)懭牖貪L段的undo log記錄绷耍。更新了行,則undo log記錄包含在更新行之前重建行內(nèi)容所需的信息鲜侥。
- 6字節(jié)的自增 (DB_ROW_ID):插入新行時(shí)單調(diào)增加的行ID褂始。
undo log
回滾使用undo log
,以rollback segment的數(shù)據(jù)結(jié)構(gòu)(Oracle也是類似數(shù)據(jù)結(jié)構(gòu))為單位進(jìn)行存儲(chǔ)描函,存儲(chǔ)在表空間中崎苗,可以說(shuō)undo log被劃分為多個(gè)段,具體某行的undo log就保存在某個(gè)段中 舀寓。因此InnoDB使用回滾段中的信息來(lái)執(zhí)行事務(wù)回滾中所需的undo操作胆数。磁盤上不存在單獨(dú)的undo log文件,所有的undo log存放在存放于ibdata文件中互墓。
具體操作:copy事務(wù)前的數(shù)據(jù)庫(kù)內(nèi)容(行)到undo buffer必尼,在適合的時(shí)間把undo buffer中的內(nèi)容刷新到磁盤。undo buffer是環(huán)形緩沖,但當(dāng)緩沖滿的時(shí)候判莉,undo buffer中的內(nèi)容會(huì)也會(huì)被刷新到磁盤undo log文件豆挽,所有的undo log均存放在主ibdata文件中(表空間),即使客戶端設(shè)置了每表一個(gè)數(shù)據(jù)文件也是如此券盅。
undo log分為insert 和update undo log類型帮哈,刪除也是update undo log。
insert undo log:原始的數(shù)據(jù)并不存在渗饮,所以回滾時(shí)把insert undo log丟棄即可
流程如下:
-
初始數(shù)據(jù)行
原始的數(shù)據(jù)并不存在但汞,回滾指針是null,回滾時(shí)直接丟棄改行。
事務(wù)1更修改
獲得記錄更新鎖后互站,把該行修改前的值Copy到undo log私蕾,記錄事務(wù)ID 回滾ID。
3.事務(wù)2修改
- 事務(wù)提交
當(dāng)事務(wù)正常提交時(shí),只需要更改事務(wù)狀態(tài)為COMMIT即可胡桃,不需做其他額外的工作踩叭,而Rollback則稍微復(fù)雜點(diǎn),需要根據(jù)當(dāng)前回滾指針從undo log中找出事務(wù)修改前的版本并恢復(fù)翠胰。
上面的流程看來(lái)容贝,undo log是不是越來(lái)越大,這點(diǎn)mysql也做了考慮之景,會(huì)啟動(dòng)purge進(jìn)程以真實(shí)刪除老的斤富、過(guò)時(shí)的數(shù)據(jù)。
redo log
為了支持事務(wù)使用redo log
锻狗。當(dāng)客戶端執(zhí)行每條SQL(更新語(yǔ)句)時(shí)满力,redo log會(huì)被首先寫入log buffer,執(zhí)行COMMIT命令時(shí)轻纪,log buffer中的內(nèi)容會(huì)被視情況刷新到磁盤油额。具體作用:保存執(zhí)行的SQL語(yǔ)句到一個(gè)指定的Log文件,當(dāng)Mysql服務(wù)因斷電恢復(fù)或強(qiáng)行重啟等時(shí)重新執(zhí)行redo log記錄的SQL操作刻帚,繼續(xù)那些已經(jīng)commit但數(shù)據(jù)尚未完全回寫到磁盤的事務(wù)潦嘶,保證操作不丟失。
與事務(wù)關(guān)系
READ_UNCOMMITTED
讀事務(wù)直接讀取主記錄崇众,無(wú)論更新事務(wù)是否完成掂僵。READ_COMMITTED
優(yōu)先讀取本事務(wù)修改的值,未有修改的每次都讀取undo log中最近的版本顷歌。兩次對(duì)同一字段的讀可能讀到不同的數(shù)據(jù)看峻,但能保證每次都讀到最新的數(shù)據(jù)。使用行鎖衙吩。REPEATABLE_READ
讀該事務(wù)第一次讀建立起來(lái)的數(shù)據(jù)快照,每次都讀取指定的版本互妓,如果沒(méi)有做更新可能讀不到最新的數(shù)據(jù)。這里使用行鎖,gap鎖冯勉,next-key鎖澈蚌,如果索引不是聚鏃索引,使用next-key鎖能保證不會(huì)產(chǎn)生幻讀灼狰。SERIALIZABLE
讀加共享鎖宛瞄,寫加排他鎖,讀讀不會(huì)阻塞交胚,讀寫或?qū)懽x出現(xiàn)阻塞份汗,因此并發(fā)度大幅下降。
參考資料
https://en.wikipedia.org/wiki/Multiversion_concurrency_control
https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html
http://www.360doc.cn/article/12904276_403505950.html