MySQL的多版本并发控制(MVCC)
點擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號
重磅資訊、干貨,第一時間送達(dá)
今日推薦:又一程序員進(jìn)了ICU:壓垮一個家庭,一張結(jié)算單就夠
個人原創(chuàng)100W+訪問量博客:點擊前往,查看更多
來源:https://segmentfault.com/a/1190000037557620
一、什么是多版本并發(fā)控制
多版本并發(fā)控制技術(shù)的英文全稱是 Multiversion Concurrency Control,簡稱 MVCC。
多版本并發(fā)控制(MVCC) 是通過保存數(shù)據(jù)在某個時間點的快照來實現(xiàn)并發(fā)控制的。也就是說,不管事務(wù)執(zhí)行多長時間,事務(wù)內(nèi)部看到的數(shù)據(jù)是不受其它事務(wù)影響的,根據(jù)事務(wù)開始的時間不同,每個事務(wù)對同一張表,同一時刻看到的數(shù)據(jù)可能是不一樣的。
簡單來說,多版本并發(fā)控制 的思想就是保存數(shù)據(jù)的歷史版本,通過對數(shù)據(jù)行的多個版本管理來實現(xiàn)數(shù)據(jù)庫的并發(fā)控制。這樣我們就可以通過比較版本號決定數(shù)據(jù)是否顯示出來,讀取數(shù)據(jù)的時候不需要加鎖也可以保證事務(wù)的隔離效果。
可以認(rèn)為 多版本并發(fā)控制(MVCC) 是行級鎖的一個變種,但是它在很多情況下避免了加鎖操作,因此開銷更低。雖然實現(xiàn)機(jī)制有所不同,但大都實現(xiàn)了非阻塞的讀操作,寫操作也只鎖定必要的行。
MySQL的大多數(shù)事務(wù)型存儲引擎實現(xiàn)的都不是簡單的行級鎖。基于提升并發(fā)性能的考慮,它們一般都同時實現(xiàn)了多版本并發(fā)控制(MVCC)。不僅是MySQL,包括Oracle、PostgreSQL等其他數(shù)據(jù)庫系統(tǒng)也都實現(xiàn)了MVCC,但各自的實現(xiàn)機(jī)制不盡相同,因為MVCC沒有一個統(tǒng)一的實現(xiàn)標(biāo)準(zhǔn),典型的有樂觀(optimistic)并發(fā)控制和悲觀(pessimistic)并發(fā)控制。
二、多版本并發(fā)控制解決了哪些問題
1. 讀寫之間阻塞的問題
通過 MVCC 可以讓讀寫互相不阻塞,即讀不阻塞寫,寫不阻塞讀,這樣就可以提升事務(wù)并發(fā)處理能力。
提高并發(fā)的演進(jìn)思路:
普通鎖,只能串行執(zhí)行;
讀寫鎖,可以實現(xiàn)讀讀并發(fā);
數(shù)據(jù)多版本并發(fā)控制,可以實現(xiàn)讀寫并發(fā)。
2. 降低了死鎖的概率
因為 InnoDB 的 MVCC 采用了樂觀鎖的方式,讀取數(shù)據(jù)時并不需要加鎖,對于寫操作,也只鎖定必要的行。
3. 解決一致性讀的問題
一致性讀也被稱為快照讀,當(dāng)我們查詢數(shù)據(jù)庫在某個時間點的快照時,只能看到這個時間點之前事務(wù)提交更新的結(jié)果,而不能看到這個時間點之后事務(wù)提交的更新結(jié)果。
三、快照讀與當(dāng)前讀
快照讀(SnapShot Read) 是一種一致性不加鎖的讀,是InnoDB并發(fā)如此之高的核心原因之一。
這里的一致性是指,事務(wù)讀取到的數(shù)據(jù),要么是事務(wù)開始前就已經(jīng)存在的數(shù)據(jù),要么是事務(wù)自身插入或者修改過的數(shù)據(jù)。
不加鎖的簡單的 SELECT 都屬于快照讀,例如:
SELECT * FROM t WHERE id=1
與 快照讀 相對應(yīng)的則是 當(dāng)前讀,當(dāng)前讀就是讀取最新數(shù)據(jù),而不是歷史版本的數(shù)據(jù)。加鎖的 SELECT 就屬于當(dāng)前讀,例如:
SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE;
SELECT * FROM t WHERE id=1 FOR UPDATE;
四、InnoDB 的 MVCC 是如何工作的
1. InnoDB 是如何存儲記錄的多個版本的
事務(wù)版本號
每開啟一個事務(wù),我們都會從數(shù)據(jù)庫中獲得一個事務(wù) ID(也就是事務(wù)版本號),這個事務(wù) ID 是自增長的,通過 ID 大小,我們就可以判斷事務(wù)的時間順序。
行記錄的隱藏列
InnoDB 的葉子段存儲了數(shù)據(jù)頁,數(shù)據(jù)頁中保存了行記錄,而在行記錄中有一些重要的隱藏字段:
DB_ROW_ID:6-byte,隱藏的行 ID,用來生成默認(rèn)聚簇索引。如果我們創(chuàng)建數(shù)據(jù)表的時候沒有指定聚簇索引,這時 InnoDB 就會用這個隱藏 ID 來創(chuàng)建聚集索引。采用聚簇索引的方式可以提升數(shù)據(jù)的查找效率。
DB_TRX_ID:6-byte,操作這個數(shù)據(jù)的事務(wù) ID,也就是最后一個對該數(shù)據(jù)進(jìn)行插入或更新的事務(wù) ID。
DB_ROLL_PTR:7-byte,回滾指針,也就是指向這個記錄的 Undo Log 信息。
Undo Log
InnoDB 將行記錄快照保存在了 Undo Log 里,我們可以在回滾段中找到它們,如下圖所示:
Undo Log回滾歷史記錄從圖中能看到回滾指針將數(shù)據(jù)行的所有快照記錄都通過鏈表的結(jié)構(gòu)串聯(lián)了起來,每個快照的記錄都保存了當(dāng)時的 db_trx_id,也是那個時間點操作這個數(shù)據(jù)的事務(wù) ID。這樣如果我們想要找歷史快照,就可以通過遍歷回滾指針的方式進(jìn)行查找。
2. 在 可重復(fù)讀(REPEATABLE READ) 隔離級別下, InnoDB 的 MVCC 是如何工作的
查詢(SELECT)
InnoDB 會根據(jù)以下兩個條件檢查每行記錄:
InnoDB只查找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(也就是,行的系統(tǒng)版本號小于或等于事務(wù)的系統(tǒng)版本號),這樣可以確保事務(wù)讀取的行,要么是在事務(wù)開始前已經(jīng)存在的,要么是事務(wù)自身插入或者修改過的。
行的刪除版本要么未定義,要么大于當(dāng)前事務(wù)版本號。這可以確保事務(wù)讀取到的行,在事務(wù)開始之前未被刪除。
只有符合上述兩個條件的記錄,才能返回作為查詢結(jié)果。
插入(INSERT)
InnoDB為新插入的每一行保存當(dāng)前系統(tǒng)版本號作為行版本號。
刪除(DELETE)
InnoDB為刪除的每一行保存當(dāng)前系統(tǒng)版本號作為行刪除標(biāo)識。刪除在內(nèi)部被視為更新,行中的一個特殊位會被設(shè)置為已刪除。
更新(UPDATE)
InnoDB為插入一行新記錄,保存當(dāng)前系統(tǒng)版本號作為行版本號,同時保存當(dāng)前系統(tǒng)版本號到原來的行作為行刪除標(biāo)識。
五、總結(jié)
多版本并發(fā)控制(MVCC) 在一定程度上實現(xiàn)了讀寫并發(fā),它只在 可重復(fù)讀(REPEATABLE READ) 和 提交讀(READ COMMITTED) 兩個隔離級別下工作。其他兩個隔離級別都和 MVCC 不兼容,因為 未提交讀(READ UNCOMMITTED),總是讀取最新的數(shù)據(jù)行,而不是符合當(dāng)前事務(wù)版本的數(shù)據(jù)行。而 可串行化(SERIALIZABLE) 則會對所有讀取的行都加鎖。
行鎖,并發(fā),事務(wù)回滾等多種特性都和MVCC相關(guān)。
參考:MySQL5.7文檔:innodb-multi-versioning《高性能MySQL》
原創(chuàng)電子書
歷時整整一年總結(jié)的?Java 面試 + Java 后端技術(shù)學(xué)習(xí)指南,這是本人這幾年及校招的總結(jié),各種高頻面試題已經(jīng)全部進(jìn)行總結(jié),按照章節(jié)復(fù)習(xí)即可,已經(jīng)拿到了大廠offer。
原創(chuàng)思維導(dǎo)圖
掃碼或者微信搜?程序員的技術(shù)圈子?回復(fù)?面試?領(lǐng)取原創(chuàng)電子書和思維導(dǎo)圖。
總結(jié)
以上是生活随笔為你收集整理的MySQL的多版本并发控制(MVCC)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 介绍一款贼美的Vue+Element开源
- 下一篇: 你还在用 Swagger?试试这个神器!