(Multiversion Concurrency Control)html
最近正在啃《高性能MySQL》這本書, 當看到事務相關知識時,決定對該知識點稍微深刻一下, 《高性能MySQL》中在介紹事務相關知識點時, 顯然不是特別深刻, 不少比較底層的知識點並無太多的深刻, 固然此處並非要對本書作什麼評判,言歸正傳, 這裏主要先說一下本人在啃相關知識點時的曲折之路:mysql
髒讀
, 不可重複讀
, 幻讀
, 更新丟失
等問題以前也有相關筆記;MVCC多版本併發控制
, 而後一發不可收拾了...下面先引用一些前輩們比較優秀的文章:git
阿里數據庫內核'2017/12'月報中對MVCC的解釋是:
多版本控制: 指的是一種提升併發的技術。最先的數據庫系統,只有讀讀之間能夠併發,讀寫,寫讀,寫寫都要阻塞。引入多版本以後,只有寫寫之間相互阻塞,其餘三種操做均可以並行,這樣大幅度提升了InnoDB的併發度。在內部實現中,與Postgres在數據行上實現多版本不一樣,InnoDB是在undolog中實現的,經過undolog能夠找回數據的歷史版本。找回的數據歷史版本能夠提供給用戶讀(按照隔離級別的定義,有些讀請求只能看到比較老的數據版本),也能夠在回滾的時候覆蓋數據頁上的數據。在InnoDB內部中,會記錄一個全局的活躍讀寫事務數組,其主要用來判斷事務的可見性。
<高性能MySQL>中對MVCC的部分介紹github
- MySQL的大多數事務型存儲引擎實現的其實都不是簡單的行級鎖。基於提高併發性能的考慮, 它們通常都同時實現了多版本併發控制(MVCC)。不只是MySQL, 包括Oracle,PostgreSQL等其餘數據庫系統也都實現了MVCC, 但各自的實現機制不盡相同, 由於MVCC沒有一個統一的實現標準。
- 能夠認爲MVCC是行級鎖的一個變種, 可是它在不少狀況下避免了加鎖操做, 所以開銷更低。雖然實現機制有所不一樣, 但大都實現了非阻塞的讀操做,寫操做也只鎖定必要的行。
- MVCC的實現方式有多種, 典型的有樂觀(optimistic)併發控制 和 悲觀(pessimistic)併發控制。
- MVCC只在
READ COMMITTED
和REPEATABLE READ
兩個隔離級別下工做。其餘兩個隔離級別夠和MVCC不兼容, 由於READ UNCOMMITTED
老是讀取最新的數據行, 而不是符合當前事務版本的數據行。而SERIALIZABLE
則會對全部讀取的行都加鎖。從書中能夠了解到:算法
- MVCC是被Mysql中
事務型存儲引擎InnoDB
所支持的;- 應對高併發事務, MVCC比
單純的加鎖
更高效;- MVCC只在
READ COMMITTED
和REPEATABLE READ
兩個隔離級別下工做;- MVCC可使用
樂觀(optimistic)鎖
和悲觀(pessimistic)鎖
來實現;- 各數據庫中MVCC實現並不統一
- 可是書中提到 "InnoDB的MVCC是經過在每行記錄後面保存兩個隱藏的列來實現的"(網上也有不少此類觀點), 但其實並不許確, 能夠參考MySQL官方文檔, 能夠看到, InnoDB存儲引擎在數據庫每行數據的後面添加了三個字段, 不是兩個!!
1.read view
, 快照snapshot
sql
淘寶數據庫內核月報/2017/10/01/
此文雖然是以PostgreSQL進行的說明, 但並不影響理解, 在"事務快照的實現"該部分有細節須要注意:
事務快照是用來存儲數據庫的事務運行狀況。一個事務快照的建立過程能夠歸納爲:
查看當前全部的未提交併活躍的事務,存儲在數組中
選取未提交併活躍的事務中最小的XID,記錄在快照的xmin中
選取全部已提交事務中最大的XID,加1後記錄在xmax中
注意: 上文中在PostgreSQL中snapshot的概念, 對應MySQL中, 其實就是你在網上看到的read view
,快照
這些概念;
好比何登成就有關於Read view
的介紹;
而 此文 卻還是使用快照
來介紹;數據庫
2.read view 主要是用來作可見性判斷的, 比較廣泛的解釋即是"本事務不可見的當前其餘活躍事務", 但正是該解釋, 可能會形成一節理解上的誤區, 因此此處提供兩個參考, 供給你們避開理解誤區:數組
read view中的`高水位low_limit_id`能夠參考 https://github.com/zhangyachen/zhangyachen.github.io/issues/68, https://www.zhihu.com/question/66320138 其實上面第1點中加粗部分也是相關高水位的介紹( 注意進行了+1 )
3.另外, 對於read view快照的生成時機, 也很是關鍵, 正是由於生成時機的不一樣, 形成了RC,RR兩種隔離級別的不一樣可見性;併發
With REPEATABLE READ isolation level, the snapshot is based on the time when the first read operation is performed. 使用REPEATABLE READ隔離級別,快照是基於執行第一個讀操做的時間。 With READ COMMITTED isolation level, the snapshot is reset to the time of each consistent read operation. 使用READ COMMITTED隔離級別,快照被重置爲每一個一致的讀取操做的時間。
4.undo-log高併發
另外, 在回滾段中的undo logs分爲: insert undo log
和 update undo log
5.InnoDB存儲引擎在數據庫每行數據的後面添加了三個字段
事務ID
(DB_TRX_ID
)字段: 用來標識最近一次對本行記錄作修改(insert|update)的事務的標識符, 即最後一次修改(insert|update)本行記錄的事務id。回滾指針
(DB_ROLL_PTR
)字段: 指寫入回滾段(rollback segment)的 undo log
record (撤銷日誌記錄記錄)。undo log
record 包含 '重建該行記錄被更新以前內容' 所必須的信息。DB_ROW_ID
字段: 包含一個隨着新行插入而單調遞增的行ID, 當由innodb自動產生彙集索引時,彙集索引會包括這個行ID的值,不然這個行ID不會出如今任何索引中。6.可見性比較算法(這裏每一個比較算法後面的描述是創建在rr級別下,rc級別也是使用該比較算法,此處未作描述)
設要讀取的行的最後提交事務id(即當前數據行的穩定事務id)爲 trx_id_current
當前新開事務id爲 new_id
當前新開事務建立的快照read view
中最先的事務id爲up_limit_id
, 最遲的事務id爲low_limit_id
(注意這個low_limit_id=未開啓的事務id=當前最大事務id+1)
比較:
trx_id_current < up_limit_id
, 這種狀況比較好理解, 表示, 新事務在讀取該行記錄時, 該行記錄的穩定事務ID是小於, 系統當前全部活躍的事務, 因此當前行穩定數據對新事務可見, 跳到步驟5.trx_id_current >= trx_id_last
, 這種狀況也比較好理解, 表示, 該行記錄的穩定事務id是在本次新事務建立以後纔開啓的, 可是卻在本次新事務執行第二個select前就commit了,因此該行記錄的當前值不可見, 跳到步驟4。trx_id_current <= trx_id_current <= trx_id_last
, 表示: 該行記錄所在事務在本次新事務建立的時候處於活動狀態,從up_limit_id到low_limit_id進行遍歷,若是trx_id_current等於他們之中的某個事務id的話,那麼不可見, 調到步驟4,不然表示可見。trx_id_current
,而後跳到步驟1從新開始判斷。1.MySQL的InnoDB存儲引擎默認事務隔離級別是RR(可重複讀), 是經過 "行排他鎖+MVCC" 一塊兒實現的, 不只能夠保證可重複讀, 還能夠部分防止幻讀, 而非徹底防止;
2.爲何是部分防止幻讀, 而不是徹底防止?
當前讀(current read)
和快照讀(snapshot read)
:3.快照讀(snapshot read)
簡單的select操做(固然不包括 select ... lock in share mode, select ... for update)
4.當前讀(current read) 官網文檔 Locking Reads
在RR級別下,快照讀是經過MVVC(多版本控制)和undo log來實現的,當前讀是經過加record lock(記錄鎖)和gap lock(間隙鎖)來實現的。
innodb在快照讀的狀況下並無真正的避免幻讀, 可是在當前讀的狀況下避免了不可重複讀和幻讀!!!
通常咱們認爲MVCC有下面幾個特色:
而InnoDB實現MVCC的方式是:
排他鎖定
,若是鎖定了還算不算是MVCC?undo log
中的內容只是串行化的結果, 記錄了多個事務的過程, 不屬於多版本共存。但理想的MVCC是難以實現的, 當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的, 能夠經過比較版本號進行回滾, 但當事務影響到多行數據時, 理想的MVCC就無能爲力了。第一類更新丟失
的狀況。