MVCC 問答

轉載自:http://qing.blog.sina.com.cn/1765738567/693f084733003vvn.html


Q:先要謝謝你的文章<海量存儲>,很系統且講清‘爲什麼’(而不是簡單的‘是什麼’),收穫不少。

看到mvcc時 http://qing.blog.sina.com.cn/1765738567/693f08473300067j.html,有一些不理解,還望詳解。


A: 

 

我先從原理開始,然後介紹mysql實現(MVCC實現非常自由。。我對一些實現也沒有那麼瞭解,有說錯的地方就海涵了)

如果要提到MVCC,就應從他最原始的需求出發來看。

對同一個數據的訪問來說,所謂的一致性和隔離性,其實就是針對以下四種情況的不同處理方式。

寫寫

寫讀

讀寫

讀讀

你可以用這四個衝突順序,拼裝出針對同一個數據的全部訪問順序。

 

那麼如何能夠限制這些請求的先後順序呢? 一個最簡單的方式就是加鎖。

第一個人訪問或寫入數據的時候,其他人不能夠寫入數據。

但這樣併發度明顯是上不去的,於是自然要想到,有沒有更快的方法呢?

那麼在java裏面,比加排他鎖更快的方法就是加入讀寫鎖咯。

它能夠保證”讀讀“ 這個衝突不會相互阻塞。

然而,讀寫和寫寫這兩個case,卻還是要全部鎖住的。

爲了解決這個問題,纔會有MVCC這個概念產生,對應java就是copyOnWrite . 

本質是爲了讓”讀寫“和”寫讀“衝突而出現的一種技術,每次針對一個數據的寫入,都會把原有數據複製一份出來,然後寫道新的地方去。這樣,這樣,如果訪問順序是寫讀寫,對於讀寫鎖來說等於加鎖三次,而對於MVCC來說只需要針對寫寫加鎖就可以了,甚至寫寫都可以不加鎖。對吧?

怎麼做到呢? 我們假定有個全局時間戳,每次事務都加1 。 那麼對於”寫讀寫“這個順序,第一個寫的時候申請事務id  0, 第二個讀因爲不修改數據所以事務id 不增加,第三個寫時事務id加1. 那麼數據的版本是0,1. 而讀數據的時候的id是0.

這樣,讀取的數據是0這個版本,因爲已經有1這個版本了,我們就可以推斷0這個版本的數據一定是完整的,而不需要關心1這個版本是否完整的寫入到了節點中,從而就不需要等待版本1的完整寫入了。於是,寫不阻塞讀,讀不阻塞寫。

從而,寫寫,寫讀,讀寫,都可以相互不阻塞,提升了系統的併發度。

 

理解了copyOnWrite,再來看真實的場景 中還需要解決什麼問題。

爲了說明這個問題,我們就需要理解什麼是事務, 所謂事務,並不是指更新或讀取一條記錄的。 事務是在事務開始後,你可能會更新的數據的集合。比如,update table set a = 1 where a > 10 . 那麼A > 10的所有數據(絕對不止一條),都需要“同時”被更改。

如果有一些計算機的基本知識,你就應該知道,計算機從本質來說還是個圖靈機實現,在目前是不可能“同時”更改所有數據的,但從需求來說又要有這樣的需求,於是只能通過其他方式模擬,這個模擬的方法,就只能是加鎖,將所有A>10的記錄都加鎖,然後再更新掉。

那麼這樣做的時候,就不大可能使用樂觀方式來做寫寫更新了,以我個人的理解,樂觀鎖的前提是,在爭用不明顯的場景下,因爲減少了上下文切換的開銷,從而可以獲得性能的提升,但如果假定我們要針對一組記錄做多次頻繁的更新時,就要權衡,到底是上下文切換的開銷大呢,還是頻繁rollback並且額外消耗大量cpu空轉的開銷大了。

於是你就知道了,爲什麼mysql 目前的實現裏面,只做到了讀不阻塞寫,寫不阻塞讀。寫和寫是相互阻塞的,原因就是因爲必須加鎖保證數據完整性。

 

----------------

針對mysql的實現方式的介紹。

爲了簡化模型,我們只討論插入的情況,實際上還有個刪除要考慮,不過原理類似。

在每行記錄上維持一個trx_id.

每一個事務開始的時候,trx_id都會增加,事務可以是顯示的setAutoCommit(false),也可以是個普通的update insert 等。

然後,他還有個當時正在進行的事務的trx_id的列表,維持在系統信息裏,所有正在運行的事務都會將自己的id記錄在這個列表中,等到事務提交後則從這個列表中刪除掉。

而每個事務又維持了”自己的“一個正在進行的事務trx_id的列表,這個列表是系統列表的一個snapshot.

 

在實際運行過程中,讀的時候其實mysql是用事務trx_id列表中最小的trx_id去與數據列中的記錄做比較的,只有比這個在當前正在運行的snapshot中最小的trx_id還要小的數據,才允許被讀取出來。

如,

全局事務id 列表是: 100,101,120,150

當前事務的id列表snapshot是 : 120,150

那麼,記錄中那些小於120 ,或者自己事務id所更新的記錄,並且符合未被刪除這個條件的的(這個原理類似,我不在這裏說是減少複雜度)。 才能夠被當前事務讀取。

然後,就來看看MVCC能做到的兩個隔離級別,讀已提交RC (其他事務提交後的數據立刻能夠被當前事務看到), 和可重複讀RR(其他事務提交後的事務在當前事務內不可見)。

瞭解了原理,不難推斷如何做到RC和RR

RC , 在事務內,在開始事務時更新一次當前事務的id列表snapshot,並且在每次運行了一個更新的sql後,都更新當前事務的id列表snapshot . 

RR , 只在事務開始時更新當前事務的id列表snapshot 。

 

這就是mysql的mvcc實現方式。

我說的那個是以oracle的實現模式爲模板的。。所以不大一樣。 PG的實現也不一樣。。不過核心思路就是我上面提到的那種。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章