MySQL探祕(八):InnoDB的事務

ztwindy發表於2018-12-23

 事務是資料庫最為重要的機制之一,凡是使用過資料庫的人,都瞭解資料庫的事務機制,也對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進行回滾操作。

資料和回滾日誌的邏輯儲存結構.jpg

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

MySQL探祕(八):InnoDB的事務

參考

相關文章