背景
并發(fā)事務(wù)可能產(chǎn)生的問題:
- 讀+讀,并發(fā)讀不會有問題
- 讀+寫,并發(fā)讀寫可能會發(fā)生臟讀、不可重復(fù)讀、幻讀
- 寫+寫,并發(fā)修改同一行數(shù)據(jù),可能產(chǎn)生數(shù)據(jù)丟失(會滾丟失、覆蓋丟失)等問題
MVCC定義
MVCC(Mutil Version Concurrency Control)多版本并發(fā)控制,是一種并發(fā)訪問的機制(非具體實現(xiàn)),廣泛應(yīng)用于數(shù)據(jù)庫管理系統(tǒng),比如Mysql、Oracle、Postgresql等,實現(xiàn)對數(shù)據(jù)庫的并發(fā)訪問。本質(zhì)就是一行數(shù)據(jù)具有多個不同版本的記錄。
Mysql的InnoDB引擎實現(xiàn)了MVCC機制,用來處理讀寫沖突,做到非阻塞并發(fā)讀,提升并發(fā)效率。
快照讀和當前讀
當前讀
讀取的是記錄的最新版本,讀取時還要保證其他并發(fā)事務(wù)不能修改當前記錄,會對讀取的記錄進行加鎖
- select lock in share mode(共享鎖) 讀讀不沖突、讀寫、寫寫沖突
- select for update ; update, insert ,delete(排他鎖) 讀讀、讀寫、寫寫沖突
快照讀
顧名思義,就是讀取undo log中的某一版本的快照,讀到的數(shù)據(jù)可能不是最新的,但是可以不加鎖就可以讀到數(shù)據(jù)
- 讀讀不沖突、讀寫不沖突
- 寫寫沖突
MVCC實現(xiàn)原理
MVCC的目的就是多版本并發(fā)控制,在數(shù)據(jù)庫的實現(xiàn),就是為了解決讀寫沖突,它的實現(xiàn)原理主要依賴記錄中的3個隱式字段,undolog,Read View來實現(xiàn)的。
隱式字段
每行記錄除了我們自定義的字段之外,還有數(shù)據(jù)庫隱式定義的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
- BD_TRX_ID: 6byte,最近修改(修改/插入)的事務(wù)ID:記錄創(chuàng)建該記錄/最后一次修改該記錄的事務(wù)ID
- DB_ROLL_PTR: 7byte,回滾指針,指向這條記錄的上一個版本(存儲在rollback segment里)
- DB_ROW_ID: 6byte,隱含自增ID,如果數(shù)據(jù)表沒有主鍵,InnoDB會自動以DB_ROW_ID產(chǎn)生一個聚簇索引
undo log
在修改數(shù)據(jù)的時候,會向 redo log 中記錄修改的頁內(nèi)容(為了在數(shù)據(jù)庫宕機重啟后恢復(fù)對數(shù)據(jù)庫的操作),也會向 undo log記錄數(shù)據(jù)原來的快照(用于回滾事務(wù))。undo log有兩個作用,除了用于回滾事務(wù),還用于實現(xiàn)MVCC
- insert log:代表事務(wù)在 insert 新記錄時產(chǎn)生的 undo log, 只在事務(wù)回滾時需要,并且在事務(wù)提交后可以被立即丟棄
- update undo log:事務(wù)在進行 update 或 delete 時產(chǎn)生的 undo log ; 不僅在事務(wù)回滾時需要,在快照讀時也需要;所以不能隨便刪除,只有在快速讀或事務(wù)回滾不涉及該日志時,對應(yīng)的日志才會被 purge 線程統(tǒng)一清除
版本鏈
1.插入一條記錄
2.修改記錄
- 加鎖
- copy到undo log,作為舊記錄(當前行的copy副本)
- 修改age,trx_id(從1開始遞增);回滾指針指向副本
- 提交事務(wù),釋放鎖
3.修改記錄
- 加鎖
- copy到undo log,作為舊記錄(當前行的copy副本),已經(jīng)有undo log,此副本作為鏈表表頭插入的undo log的頭節(jié)點
- 修改age,trx_id(從1開始遞增);回滾指針指向副本
- 提交事務(wù),釋放鎖
Read View讀視圖
Read View是在對數(shù)據(jù)進行快照讀時,會產(chǎn)生的一個”一致性讀視圖“。
屬性:
- m_ids:活躍事務(wù)id列表,當前系統(tǒng)中所有活躍的(也就是沒提交的)事務(wù)的事務(wù)id列表。
- min_trx_id:m_ids 中最小的事務(wù)id。
- max_trx_id:生成 ReadView 時,系統(tǒng)應(yīng)該分配給下一個事務(wù)的id(注意不是 m_ids 中最大的事務(wù)id),也就是m_ids 中的最大事務(wù)id + 1 。
- creator_trx_id:生成該 ReadView 的事務(wù)的事務(wù)id。
這些屬性組成了當前事務(wù)的一致性視圖(Read View),而數(shù)據(jù)版本的可見性規(guī)則,就是基于數(shù)據(jù)的 row trx_id 和這個一致性視圖的對比結(jié)果得到的。
Read View可見性算法
把數(shù)據(jù)的最新記錄中的 DB_TRX_ID取出來,與Read View對比,如果不符合可見性,那就通過undo log 取下一個版本對比,直到找到滿足可見性的版本數(shù)據(jù)。文章來源:http://www.zghlxwxcb.cn/news/detail-422663.html
- 當【版本鏈中記錄的 trx_id 等于當前事務(wù)id(trx_id = creator_trx_id)】時,說明版本鏈中的這個版本是當前事務(wù)修改的,所以該快照記錄對當前事務(wù)可見。
- 當【版本鏈中記錄的 trx_id 小于活躍事務(wù)的最小id(trx_id < min_trx_id)】時,說明版本鏈中的這條記錄已經(jīng)提交了,所以該快照記錄對當前事務(wù)可見。
- 當【版本鏈中記錄的 trx_id 大于下一個要分配的事務(wù)id(trx_id > max_trx_id)】時,該快照記錄對當前事務(wù)不可見。
- 當【版本鏈中記錄的 trx_id 大于等于最小活躍事務(wù)id】且【版本鏈中記錄的trx_id小于下一個要分配的事務(wù)id】(min_trx_id<= trx_id < max_trx_id)時,
- 如果trx_id m_ids中,說明生成 ReadView 時,修改記錄的事務(wù)還沒提交,所以該快照記錄對當前事務(wù)不可見;
- 如果trx_id不在m_ids中,說明生成該版本的事務(wù)已經(jīng)提交,對當前事務(wù)可見
隔離級別
- 讀未提交(READ UNCOMMITTED)
- 讀已提交(READ COMMITTED)
- 可重復(fù)讀(REPEATABLE READ)
- 串行化(SERIALIZABLE)
mvcc只在讀已提交和可重復(fù)讀兩種隔離級別生效文章來源地址http://www.zghlxwxcb.cn/news/detail-422663.html
- 讀已提交
- 事務(wù)開啟后,每次select都會生成一個Read View,可以讀到別的事務(wù)已經(jīng)提交的數(shù)據(jù)
- 可重復(fù)讀
- 事務(wù)開啟后,只在第一次select時生成Read View,之后的select都基于此視圖做可見性判斷。
長事務(wù)
為什么要避免長事務(wù)
- 長事務(wù)可能存在很老的Read View,如下圖的事務(wù)1和2,這些視圖很可能訪問任何數(shù)據(jù),在這個事務(wù)提交前,它可能用到的回滾記錄都不能清理,需要保留,占用大量存在空間
- 長事務(wù)占用鎖資源,長時間不釋放鎖,可能拖垮整個庫
到了這里,關(guān)于Mysql MVCC實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!