MySQL探祕(八):InnoDB的事務

 事務是數據庫最爲重要的機制之一,凡是使用過數據庫的人,都瞭解數據庫的事務機制,也對ACID四個基本特性如數家珍。但是聊起事務或者ACID的底層實現原理,往往言之不詳,不明所以。所以,今天我們就一起來分析和探討InnoDB的事務機制,希望能建立起對事務底層實現原理的具體瞭解。

事務的四大特性

 數據庫事務具有ACID四大特性。ACID是以下4個詞的縮寫:

  • 原子性(atomicity) :事務最小工作單元,要麼全成功,要麼全失敗 。
  • 一致性(consistency): 事務開始和結束後,數據庫的完整性不會被破壞 。
  • 隔離性(isolation) :不同事務之間互不影響,四種隔離級別爲RU(讀未提交)、RC(讀已提交)、RR(可重複讀)、SERIALIZABLE (串行化)。
  • 持久性(durability) :事務提交後,對數據的修改是永久性的,即使系統故障也不會丟失 。

 下面,我們就以一個具體實例來介紹數據庫事務的原理,並介紹InnoDB是如何實現ACID四大特性的。

示例介紹

 我們首先來看一下具體的示例。大家可以自己親自試驗一下,這樣理解和記憶都會更加深刻。
 首先,使用如下的SQL語句創建兩張表,分別是goods和trade,代表貨物和交易。並向goods表中插入一條記錄,id爲1的貨物數量爲10。

CREATE TABLE goods (id INT, num INT, PRIMARY KEY(id));
CREATE TABLE trade (id INT, goods_id INT, user_id INT, PRIMARY KEY(id));
INSERT INTO goods VALUES(1, 10);

 然後打開終端,連接數據庫,開啓會話一,先用BEGIN顯示開啓一個事務。會話一先將goods表中id爲1的貨物的數量減一,然後向trade表中添加一筆交易的記錄,最後使用COMMIT顯示提交事務。
 而會話二則先查詢goods表中id爲1的貨物數量,然後向trade表中添加一筆交易記錄,接着更新goods表中id爲1的貨物的數量,最後使用ROLLBACK進行事務的回滾。其中,兩個會話中執行的具體語句和先後順序如下圖所示。

示例具體語句和執行順序

 這個示例可以體現數據庫事務的很多特性,我們一一來介紹。首先會話一的操作2更新了id爲1的貨物的數量,但是會話二的操作5讀出來的數量仍然是10,這體現了事務的隔離性,使用InnoDB的多版本控制機制實現。

 會話二的操作7也要更新同種貨物的數量,此時因爲會話一的操作2已經更新了該貨物的數量,InnoDB已經鎖住了該記錄的行鎖,所以操作7會被阻塞,直到會話一COMMIT。但是會話一的操作4和會話二的操作7都是向trade表中插入記錄,後者卻不會因爲前者而阻塞,因爲二者插入的不是同一行記錄。鎖機制是一種常見的併發控制機制,它和多版本控制機制一起實現了InnoDB事務的隔離性,關於InnoDB鎖相關的具體內容可以參考InnoDB鎖的類型和狀態查詢InnoDB行鎖算法

 會話一事務最終使用COMMIT提交了事務而會話二事務則使用ROLLBACK回滾了整個事務,這體現了事務的原子性。即事務的一系列操作要麼全部執行(COMMIT),要麼就全部不執行(ROLLBACK),不存在只執行一部分的情況。InnoDB使用事務日誌系統來實現事務的原子性。這裏有的同學就會問了,如果中途連接斷開或者Server Crash會怎麼樣。能怎麼樣,直接自動回滾唄。

 一旦會話一使用COMMIT操作提交事務成功後,那麼數據一定會被寫入到數據庫中並持久的存儲起來,這體現了事務的持久性。InnoDB使用redo log機制來實現事務的持久性。

 而事務的一致性比較難以理解,簡單的講在事務開始時,此時數據庫有一種狀態,這個狀態是所有的MySQL對象處於一致的狀態,例如數據庫完整性約束正確,日誌狀態一致等。當事務提交後,這時數據庫又有了一個新的狀態,不同的數據,不同的索引,不同的日誌等。但此時,約束,數據,索引,日誌等MySQL各種狀態還是要保持一致性。 也就是說數據庫從一個一致性的狀態,變到另一個一致性的狀態。事務執行後,並沒有破壞數據庫的完整性約束。

 下面我們就來詳細講解一下上述示例涉及的事務的ACID特性的具體實現原理。總結來說,事務的隔離性由多版本控制機制和鎖實現,而原子性、一致性和持久性通過InnoDB的redo log、undo log和Force Log at Commit機制來實現

原子性,持久性和一致性

 原子性,持久性和一致性主要是通過redo log、undo log和Force Log at Commit機制機制來完成的。redo log用於在崩潰時恢復數據,undo log用於對事務的影響進行撤銷,也可以用於多版本控制。而Force Log at Commit機制保證事務提交後redo log日誌都已經持久化。
 開啓一個事務後,用戶可以使用COMMIT來提交,也可以用ROLLBACK來回滾。其中COMMIT或者ROLLBACK執行成功之後,數據一定是會被全部保存或者全部回滾到最初狀態的,這也體現了事務的原子性。但是也會有很多的異常情況,比如說事務執行中途連接斷開,或者是執行COMMIT或者ROLLBACK時發生錯誤,Server Crash等,此時數據庫會自動進行回滾或者重啓之後進行恢復。

 我們先來看一下redo log的原理,redo log顧名思義,就是重做日誌,每次數據庫的SQL操作導致的數據變化它都會記錄一下,具體來說,redo log是物理日誌,記錄的是數據庫頁的物理修改操作。如果數據發生了丟失,數據庫可以根據redo log進行數據恢復。

 InnoDB通過Force Log at Commit機制實現事務的持久性,即當事務COMMIT時,必須先將該事務的所有日誌都寫入到redo log文件進行持久化之後,COMMIT操作纔算完成。
 當事務的各種SQL操作執行時,即會在緩衝區中修改數據,也會將對應的redo log寫入它所屬的緩存。當事務執行COMMIT時,與該事務相關的redo log緩衝必須都全部刷新到磁盤中之後COMMIT纔算執行成功。

數據庫日誌和數據落盤機制

 redo log寫入磁盤時,必須進行一次操作系統的fsync操作,防止redo log只是寫入了操作系統的磁盤緩存中。參數innodb_flush_log_at_trx_commit可以控制redo log日誌刷新到磁盤的策略,它的具體作用可以查閱InnoDB的磁盤文件及落盤機制

 redo log全部寫入磁盤後事務就算COMMIT成功了,但是此時事務修改的數據還在內存的緩衝區中,稱其爲髒頁,這些數據會依據檢查點(CheckPoint)機制擇時刷新到磁盤中,然後刪除相應的redo log,但是如果在這個過程中數據庫Crash了,那麼數據庫重啓時,會依據redo log file將那些還在內存中未更新到磁盤上的數據進行恢復。

 數據庫爲了提高性能,數據頁在內存修改後並不是每次都會刷到磁盤上。而是引入checkpoint機制,擇時將數據頁落盤,checkpoint記錄之前的數據頁保證一定落盤了,這樣相關的redo log就沒有用了(由於InnoDB redo log file循環使用,這時這部分日誌就可以被覆蓋),checkpoint之後的數據頁有可能落盤,也有可能沒有落盤,所以checkpoint之後的redo log file在崩潰恢復的時候還是需要被使用的。InnoDB會依據髒頁的刷新情況,定期推進checkpoint,從而減少數據庫崩潰恢復的時間。檢查點的信息在第一個日誌文件的頭部。

 數據庫崩潰重啓後需要從redo log中把未落盤的髒頁數據恢復出來,重新寫入磁盤,保證用戶的數據不丟失。當然,在崩潰恢復中還需要回滾沒有提交的事務。由於回滾操作需要undo日誌的支持,undo日誌的完整性和可靠性需要redo日誌來保證,所以崩潰恢復先做redo恢復數據,然後做undo回滾。

 在事務執行的過程中,除了記錄redo log,還會記錄一定量的undo log。undo log記錄了數據在每個操作前的狀態,如果事務執行過程中需要回滾,就可以根據undo log進行回滾操作。

數據和回滾日誌的邏輯存儲結構

 undo log的存儲不同於redo log,它存放在數據庫內部的一個特殊的段(segment)中,這個段稱爲回滾段。回滾段位於共享表空間中。undo段中的以undo page爲更小的組織單位。undo page和存儲數據庫數據和索引的頁類似。因爲redo log是物理日誌,記錄的是數據庫頁的物理修改操作。所以undo log的寫入也會產生redo log,也就是undo log的產生會伴隨着redo log的產生,這是因爲undo log也需要持久性的保護。如上圖所示,表空間中有回滾段和葉節點段和非葉節點段,而三者都有對應的頁結構。

 我們再來總結一下數據庫事務的整個流程,如下圖所示。

事務的相關流程

 事務進行過程中,每次sql語句執行,都會記錄undo log和redo log,然後更新數據形成髒頁,然後redo log按照時間或者空間等條件進行落盤,undo log和髒頁按照checkpoint進行落盤,落盤後相應的redo log就可以刪除了。此時,事務還未COMMIT,如果發生崩潰,則首先檢查checkpoint記錄,使用相應的redo log進行數據和undo log的恢復,然後查看undo log的狀態發現事務尚未提交,然後就使用undo log進行事務回滾。事務執行COMMIT操作時,會將本事務相關的所有redo log都進行落盤,只有所有redo log落盤成功,纔算COMMIT成功。然後內存中的數據髒頁繼續按照checkpoint進行落盤。如果此時發生了崩潰,則只使用redo log恢復數據。

隔離性

 InnoDB事務的隔離性主要通過多版本控制機制和鎖機制實現,具體可以參考多版本控制InnoDB鎖的類型和狀態查詢InnoDB行鎖算法三篇文章。

後記

 本來想一篇文章將MySQL的事務機制講明白,寫完自己讀了一遍,還是發現內容有些晦澀難懂,複雜的知識本來就是很難講明白的,夫夷以近,則遊者衆;險以遠,則至者少,希望讀者以本文作爲一篇指引性的文章,自己再去更加深入的地方去探祕。不過,能將複雜知識講解的通俗簡單也是一項很大的本領,文字和講解能力還是需要提示的。

參考

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