MySQL是怎麼保證資料一致性的

普通程式設計師發表於2022-12-05

在《寫資料庫同時發mq訊息事務一致性的一種解決方案》一文的方案中把分散式事務巧妙轉成了資料庫事務。我們都知道關係型資料庫事務能保證資料一致性,那資料庫到底是怎麼設計事務這一特性的呢?

一、MySQL事務模型ACID

MySQL是一個多引擎資料庫,其中InnoDB支援資料庫事務,也是最常用的引擎。下邊就介紹InnoDB的事務模型

MySQL官方文件對事務是這麼描述的“事務是可以提交或回滾的原子工作單元當事務對資料庫進行多個更改時,要麼提交事務時所有更改都成功,要麼回滾事務時撤消所有更改。

ACID模型是一組資料庫設計原則,強調業務資料和關鍵應用程式的可靠性很重要。MySQL包含與ACID模型緊密結合的innodb儲存引擎元件,確保資料不會被破壞,結果不會被軟體崩潰和硬體故障等異常情況所篡改。當您依賴ACID的特性,就不再需要重新發明一致性檢查和崩潰恢復機制。

ACID模型按照字母拆解分為四大特性

A : atomicity 原子性。原子性是我們對事務最直觀的理解:事務就是一系列的操作,要麼全部都執行,要麼全部都不執行。

C : consistency 一致性。資料庫事務不能破壞關係資料的完整性以及業務邏輯上的一致性。例如對銀行轉帳事務,不管事務成功還是失敗,應該保證事務結束後ACCOUNTS表中Tom和Jack的存款和不變。

I : isolation 隔離性。在併發環境中,當不同的事務同時操縱相同的資料時,每個事務都有各自的完整資料空間。

D : durability 永續性。只要事務成功結束,它對資料庫所做的更新就必須永久儲存下來。即使發生系統崩潰,重新啟動資料庫系統後,資料庫還能恢復到事務成功結束時的狀態。

二、InnoDB儲存引擎架構

下邊這張圖是InnoDB的架構,包括兩大部分,記憶體結構(In-Memory Structures)和磁碟上的結構(On-Disk Structures)。

在這張圖中,尤其要關注“Redo Log”和“Undo Tablespaces”這兩個區域,它們跟事務息息相關。 

MySQL是怎麼保證資料一致性的

記憶體結構(In-Memory Structures)更多的目的是在提高效能,因此本文不會過多關注。如果感興趣,可以訪問MySQL的官方網站

“Undo Tablespaces”包含Undo Log(撤消日誌),Undo Log是撤消日誌記錄的集合,其中包含如何撤消事務對聚集索引記錄的最新更改的資訊。Undo Log存在於撤消日誌段中,這些日誌段包含在回滾段中。

MySQL事務的四個特性中ACD三個特性是透過Redo Log(重做日誌)和Undo Log 實現的,而 I(隔離性)是透過Lock(鎖)來實現。

三、普及個概念MVCC

MVCC,Multi-Version Concurrency Control,多版本併發控制這項技術使得InnoDB的事務隔離級別下執行一致性讀操作有了保證,換言之,就是為了查詢一些正在被另一個事務更新的行,並且可以看到它們被更新之前的值。這是一個可以用來增強併發性的強大技術,查詢不用等待另一個事務釋放鎖。這項技術廣泛應用於資料庫,例如Oracle,PostgreSQL。當然也有一些資料庫產品以及mysql的其它儲存引擎不支援它。

看一看MVCC機制的示意圖,圖下邊會給出文字解釋 

MySQL是怎麼保證資料一致性的

圖中底部橫軸是時間,縱向的箭頭用來標記增、刪、改、查發生的時刻。尤其注意時間軸上方兩條色塊,代表資料的兩個版本V1、V2。為了醒目,我把V1、V2用紅色方框圈了起來(多版本的體現)。從左向右解讀這張圖

1、T1事務插入資料a=3,然後提交,生成了資料對應的V1版本

2、T2事務開始讀取a資料,讀取會持續一段時間,由於開始讀取的時刻,只有V1版本,所以最終T2讀到a=3

3、T2讀取過程中,T3對資料a進行修改,a=4,生成a資料的V2版本,但此時並未提交,因此生效的是V1版本資料。

4、T3修改提交之前,T4讀取a資料,由於此時V1版本資料生效,因此,T4讀到a=3

5、T3提交a=4的修改,V1版本資料失效,V2生效。a的值變為4

6、T5讀取a的值,讀到V2版本,a=4

至此,MVCC的概念就搞明白了,那麼MySQL是怎麼實現的呢?

四、InnoDB多版本的實現

1、三個隱藏欄位

在內部,InnoDB向資料庫中儲存的每一行資料新增三個欄位。

(1)DB_TRX_ID欄位,6位元組。表示插入或更新行的最後一個事務的事務識別符號。此外,刪除在內部被視為更新,其中行中的特殊位被設定為將其標記為已刪除。

(2)DB_ROLL_PTR欄位,7位元組,叫做回滾指標(roll pointer)。回滾指標指向寫入回滾段的撤消日誌(Undo Log)。如果行已更新,則撤消日誌包含重建更新前該行內容所需的資訊。

(3)DB_ROW_ID欄位,6位元組。包含一個隨著新行插入而單調增加的行ID,如果innodb自動生成聚集索引,則該索引包含行ID值。否則,DB_ROW_ID列不會出現在任何索引中。

2、多版本產生過程

以新增一條記錄並對該記錄進行2次修改來說明具體實現 

MySQL是怎麼保證資料一致性的

這條記錄有3個隱含欄位(前面已經介紹),分別應對行的ID、事務號和回滾指標。

當插入的是一條新資料時,記錄上對應的回滾段指標為NULL 

MySQL是怎麼保證資料一致性的

這個過程做了以下幾件事

  • 排他鎖鎖定該行

  • 把該行修改前的值複製到Undo Log中

  • 修改當前行的值,填寫事務編號,使回滾指標指向Undo Log中的修改前的行

  • 記錄Redo Log,包括Undo Log中的變化 

MySQL是怎麼保證資料一致性的

多次更新後,回滾指標會把不同版本的記錄串在一起。在InnoDB中存在purge執行緒,它會查詢那些比現在最老的活動事務還早的Undo Log,並刪除它們,從而保證Undo Log檔案不至於無限增長。

3、提交與回滾

當事務正常提交時,InnoDB只需要更改事務狀態為commit即可,不需要做其他額外的工作

回滾(rollback)需要根據當前回滾指標從Undo Log中找出事務修改前的版本,並恢復。如果事務影響的行非常多,回滾則可能會很慢,根據經驗值沒提交的事務行數在1000~10000之間,InnoDB效率還是非常高的(唐成-資料庫多版本實現內幕)。

commit效率高,rollback代價大

4、可見性

事務隔離是資料庫處理的基礎之一,隔離是縮寫ACID中的I。隔離級別是當多個事務同時進行更改和執行查詢時,微調效能、可靠性、一致性和結果再現性之間的平衡的設定。

InnoDB提供SQL1992標準定義的四個隔離級別,READ UNCOMMITTED(未提交讀), READ COMMITTED(已提交讀), REPEATABLE READ(可重複讀), and SERIALIZABLE(可序列化)。預設的是REPEATABLE READ

每種隔離級別具體的意義可以百度查到,實現原理深入進去比較複雜。注意到每條資料隱藏的事務ID欄位DB_TRX_ID有時序性,理論上可以根據一些策略,藉助這個欄位來實現與隔離級別相關的功能。事實上InnoDB也是這麼做的。當然這個功能還涉及很多的問題,這裡不再展開。

MySQL官方文件在“鎖和事務模型”這一章節開始就介紹了InnoDB的鎖,截個目錄,感興趣可以去讀一下。 

MySQL是怎麼保證資料一致性的

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556438/viewspace-2662580/,如需轉載,請註明出處,否則將追究法律責任。

相關文章