MVCC(Multi-Version Concurrency Control)肪获,多版本并發(fā)控制寝凌。
MVCC是一種并發(fā)控制的方法,通過維護數(shù)據(jù)多個版本的記錄孝赫,以無鎖的方式解決并發(fā)讀寫沖突较木。目的就是規(guī)避在讀寫沖突的時候進行加鎖的操作。Mysql的Innodb引擎就使用了MVCC青柄。
相關概念
要了解MVCC的實現(xiàn)機制伐债,需要先知道Mysql中以下幾個概念。
事物ID和DB_TRX_ID
事物ID
我們都知道innodb是支持事物的致开,在innodb中每一個事物創(chuàng)建時都會分配一個自增的ID作為事物為唯一標志峰锁,也就是事物ID。
DB_TRX_ID
數(shù)據(jù)表里每一行數(shù)據(jù)都會有一個隱藏字段DB_TRX_ID双戳,用來存儲創(chuàng)建或者最后一次修改此記錄的事物ID
undo log和DB_ROLL_PTR
DB_ROLL_PTR
數(shù)據(jù)表里另外一個隱藏字段虹蒋,DB_ROLL_PTR回滾指針,指向這條記錄的上一個版本在undo log中的數(shù)據(jù)。
undo log
undo log存儲每行記錄的修改歷史魄衅∏涂ⅲ可以簡單理解undo log是一個與數(shù)據(jù)表結構相同的另外一張表,數(shù)據(jù)表的數(shù)據(jù)行字段它都有晃虫,數(shù)據(jù)表的行記錄每修改一次皆撩,就將這行數(shù)據(jù)的當前記錄寫到undo log中,并將返回的undo log指針地址寫入DB_ROLL_PTR傲茄,然后修改數(shù)據(jù)行數(shù)據(jù)毅访、更新事物ID听隐。
undo log主要分為兩種:
insert undo log
代表事務在insert新記錄時產(chǎn)生的undo log, 只在事務回滾時需要潮孽,并且在事務提交后可以被立即丟棄
update undo log
事務在進行update或delete時產(chǎn)生的undo log; 不僅在事務回滾時需要掏呼,在快照讀時也需要;所以不能隨便刪除草巡,只有在快速讀或事務回滾不涉及該日志時,對應的日志才會被purge線程統(tǒng)一清除
結構大概如下圖所示:
undo log可以作為mvcc中查找對應可讀記錄型酥,也可以作為當前事物的rollback依據(jù)山憨。
undo log也并不是無限增長的,會有另外一個線程會嘗試清除早期的undo log記錄弥喉,因為他們已經(jīng)沒有用處了郁竟。
當前讀與快照讀
當前讀
當前讀指的是讀取數(shù)據(jù)當前最新數(shù)據(jù)。update由境、insert棚亩、delete、select for update(排他鎖)虏杰、select lock in share mode讥蟆。讀取數(shù)據(jù)需要保證其他并發(fā)事務不能修改當前記錄,會對讀取的記錄進行加鎖纺阔。
快照讀
快照讀指的是在讀取數(shù)據(jù)時瘸彤,生成讀取快照,在同一個事物中可能會一直讀取此快照的數(shù)據(jù)笛钝≈士觯快照讀讀到的數(shù)據(jù)可能不是最新的,可能是歷史版本的數(shù)據(jù)玻靡,這些歷史版本的數(shù)據(jù)就是從undo log中獲取的结榄。
事物中的select 不加鎖的情況會執(zhí)行快照讀,快照讀依賴readview來實現(xiàn)啃奴。
快照讀的前提是隔離級別不是串行級別潭陪,串行級別下的快照讀會退化成當前讀。
ReadView
讀視圖,由當前活躍事物ID的列表trx_list依溯、當前活躍最小事物ID low_limit_id老厌、下一個即將分配的事物ID up_limit_id,三個部分組成黎炉。
事物間可見性分析
基于ReadView的可見性分析邏輯
執(zhí)行快照讀的時候枝秤,會創(chuàng)建一個ReadView。定義被讀取行的DB_TRX_ID 為trx_id慷嗜。
- 比較 trx_id是否小于low_limit_id 或者為當前事物ID淀弹,如果為true,則代表修改此行數(shù)據(jù)的事物早已提交或者就是當前事務進行的修改庆械,當前記錄可見薇溃。否則進入下一步判斷。
- 比較trx_id是否大于等于up_limit_id缭乘,如果為true沐序,則代表修改此行記錄的事物晚于當前讀視圖創(chuàng)建,當前記錄不可見堕绩,根據(jù)DB_ROLL_PTR undo log指針找到上一條記錄策幼,從新進行可見性分析。否則進入下一步判斷
- 判斷trx_id是否在trx_list列表中奴紧,如果在特姐,代表修改此行記錄的事物還未提交,當前事務不可以讀取當前記錄黍氮,根據(jù)DB_ROLL_PTR undo log指針找到上一條記錄唐含,從新進行可見性分析。否則說明數(shù)據(jù)在readview生成的時候已經(jīng)提交滤钱,當期事物可以讀取當前記錄觉壶。
數(shù)據(jù)庫事物隔離級別與MVCC
- 臟讀
讀到了別的事物未提交的數(shù)據(jù),由于別的事物有可能會回滾件缸,相當于讀到了錯誤的數(shù)據(jù)铜靶。 - 不可重復讀
讀到了別的事物已提交的數(shù)據(jù),在當前事務中他炊,前后兩次的讀取可能數(shù)據(jù)不一致争剿。 - 幻讀
也是讀到了別的事物已提交的數(shù)據(jù),在當前事務中痊末,前后兩次的讀取可能數(shù)據(jù)不一致蚕苇。與不重復讀區(qū)別是,幻讀指的是insert或delete產(chǎn)生的不一致凿叠,而不可重讀指的是update產(chǎn)生的不一致涩笤。
數(shù)據(jù)庫為了解決臟讀嚼吞、不可重復讀、幻讀蹬碧,定義了事物間的隔離級別舱禽。
事物隔離級別
- 串行化Serializable
一切指令同步執(zhí)行,也就沒有以上的問題了恩沽√苤桑可以解決臟讀、幻讀罗心、不可重讀里伯。 - 可重復讀Repeat Read、RR
在同一事物中讀取被修改的記錄渤闷,總是一致的疾瓮。可以解決臟讀飒箭、不可重復讀爷贫。 - 讀已提交Read Committed、RC
可以讀取別的事物已經(jīng)提交的數(shù)據(jù)补憾。可以解決臟讀卷员。 - 讀未提交Read UnCommitted
可以讀到別的事物未提交的數(shù)據(jù)盈匾。啥問題都沒解決。
隔離級別是約嚴格需要的約束越多毕骡,相對的性能就會越差削饵。
Mysql Innodb的默認數(shù)據(jù)庫隔離級別為RR。
Oracle的隔離級別只支持Serializable和RC未巫,另外提供了一種只讀的模式窿撬,只允許select。
事物隔離級別與MVCC
在RR級別下叙凡,事物進行快照讀時會檢查當前事物是否已經(jīng)創(chuàng)建過ReadView劈伴,如果存在,則使用已經(jīng)創(chuàng)建的握爷,這也是實現(xiàn)可重復的方法跛璧。
在RC級別下,事物每一次快照讀都會創(chuàng)建一個新的ReadView新啼,這樣就會造成不可重復的和幻讀的問題追城。
Innodb利用MVCC解決了RR級別下快照讀中的幻讀問題,當前讀中的幻讀問題需要使用GAP lock解決燥撞,也就是間隙鎖座柱。
舉個例子:
創(chuàng)建user表迷帜,自增主鍵ID、name色洞、age戏锹,三個字段;
插入兩條數(shù)據(jù)1-張三-10锋玲、2-李四-20景用;
啟動事物1,查詢name=張三的記錄惭蹂,會返回1-張三伞插;
啟動事物2,新增記錄3-張三-30盾碗;
在事物1中再次查詢name=張三的記錄媚污,仍然返回1-張三;
在事物1中修改name=張三的age=45廷雅,提交耗美;會發(fā)現(xiàn)1、3的age都會變?yōu)?5航缀。
這就是解決了快照讀的幻讀商架,而修改操作屬于當前讀,仍然有幻讀的問題芥玉。
但是如果這里將事物2的提交事務延遲到事物1修改數(shù)據(jù)之后蛇摸,會發(fā)現(xiàn)事物1的修改數(shù)據(jù)會被卡住。
這里就使用了間隙鎖進行防止寫的幻讀灿巧。