1. 程式人生 > 其它 >淺談MySQL中的MVCC

淺談MySQL中的MVCC

MVCC(multiversion concurrency control),多版本併發控制,在MySQL資料庫中主要是通過在每一行記錄中增加三個欄位,與undo log 中相關記錄配合使用,同時加上可見性演算法,使得各個事務可以在不加鎖的情況下能夠同時地讀取到某行記錄上的準確值(這個值對不同的事務而言可能是不同的)。使用MVCC,在不加鎖的情況下也能讀取到準確的資料,大大提高了併發效率。本文我們就來講MySQL中的MVCC。

那麼為什麼需要MVCC呢?資料庫通常使用鎖來實現隔離性。最原生的鎖,鎖住一個資源後會禁止其他任何執行緒訪問同一個資源。但是很多應用的一個特點都是讀多寫少的場景,很多資料的讀取次數遠大於修改的次數,而讀取資料間互相排斥顯得不是很必要。所以就使用了一種讀寫鎖的方法,讀鎖和讀鎖之間不互斥,而寫鎖和寫鎖、讀鎖都互斥。這樣就很大提升了系統的併發能力。之後人們發現併發讀還是不夠,又提出了能不能讓讀寫之間也不衝突的方法,就是讀取資料時通過一種類似快照的方式將資料儲存下來,這樣讀鎖就和寫鎖不衝突了,不同的事務session會看到自己特定版本的資料。當然快照是一種概念模型,不同的資料庫可能用不同的方式來實現這種功能。

舉個例子,程式設計師A正在讀資料庫中某些內容,而程式設計師B正在給這些內容做修改(假設是在一個事務內修改,大概持續10s左右),A在這10s內 則可能看到一個不一致的資料,在B沒有提交前,如何讓A能夠一直讀到的資料都是一致的呢?

有幾種處理方法,第一種: 基於鎖的併發控制,程式設計師B開始修改資料時,給這些資料加上鎖,程式設計師A這時再讀,就發現讀取不了,處於等待情況,只能等B操作完才能讀資料,這保證A不會讀到一個不一致的資料,但是這個會影響程式的執行效率。還有一種就是:MVCC,每個使用者連線資料庫時,看到的都是某一特定時刻的資料庫快照,在B的事務沒有提交之前,A始終讀到的是某一特定時刻的資料庫快照,不會讀到B事務中的資料修改情況,直到B事務提交,才會讀取B的修改內容。

一個支援MVCC的資料庫,在更新某些資料時,並非使用新資料覆蓋舊資料,而是標記舊資料是過時的,同時在其他地方新增一個數據版本。因此,同一份資料有多個版本儲存,但只有一個是最新的。

MVCC提供了時間一致性的處理思路,在MVCC下讀事務時,通常使用一個時間戳或者事務ID來確定訪問哪個狀態的資料庫及哪些版本的資料。讀事務跟寫事務彼此是隔離開來的,彼此之間不會影響。假設同一份資料,既有讀事務訪問,又有寫事務操作,實際上,寫事務會新建一個新的資料版本,而讀事務訪問的是舊的資料版本,直到寫事務提交,讀事務才會訪問到這個新的資料版本。

MVCC有兩種實現方式,第一種實現方式是將資料記錄的多個版本儲存在資料庫中,當這些不同版本資料不再需要時,垃圾收集器回收這些記錄。這個方式被PostgreSQL和Firebird/Interbase採用,SQL Server使用的類似機制,所不同的是舊版本資料不是儲存在資料庫中,而儲存在不同於主資料庫的另外一個數據庫tempdb中。

第二種實現方式只在資料庫儲存最新版本的資料,但是會在使用undo時動態重構舊版本資料,這種方式被Oracle和MySQL/InnoDB使用。

MVCC只在 READ COMMITTED 和 REPEATABLE READ 兩個隔離級別下工作。其他兩個隔離級別夠和MVCC不相容, 因為 READ UNCOMMITTED 總是讀取最新的資料行, 而不是符合當前事務版本的資料行。而 SERIALIZABLE 則會對所有讀取的行都加鎖。如果你是可重複讀隔離級別REPEATABLE_READ,這時候你的ReadView還是第一次select時候生成的ReadView,也就是列表的值還是[100]。所以select的結果是強哥1。所以第二次select結果和第一次一樣,所以叫可重複讀!也就是說已提交讀隔離級別下的事務在每次查詢的開始都會生成一個獨立的ReadView,而可重複讀隔離級別則在第一次讀的時候生成一個ReadView,之後的讀都複用之前的ReadView。

以上就是MySQL中的MVCC,通過版本鏈,實現多版本,可併發讀-寫,寫-讀。通過ReadView生成策略的不同實現不同的隔離級別。