1. 程式人生 > >讀書筆記之InnoDB事務

讀書筆記之InnoDB事務

事務介紹


事務( Transaction) 是資料庫區別於檔案系統的重要特性 之一,也是MySQL裡面InnoDB引擎的一個重要核心功能。InnoDB 儲存引擎中的事務完全符合 ACID 的 特性。 ACID 是以下4個詞的縮寫:

❑ 原子性(atomicity),事務具有的原子性,是指事務操作要麼全部執行,要麼全部不執行,不會出現部分執行部分沒執行的情況,比如一個事務要修改10條記錄,要麼就全部10條都修改成功,要麼全部10條都不修改,而不會出現修改了5條剩下的5條沒修改。總的來講就是事務所包含的一系列操作不會被分割成若干子操作,他必定是連續的,像原子那樣不能再細了不能再分割的意思。


❑ 一致性(consistency),一致性是指資料庫從一個狀態轉變為下一個狀態,事務在開始和結束以後,資料庫的完整性約束沒有被破壞。個人的理解是,從資料庫讀取出來的資料都是完整的滿足約束的狀態,而不會出現一箇中間狀態,比如某資料庫表格test欄位A是唯一約束,即表示test表格欄位A不允許有重複相同的記錄。

那麼事務對錶格test進行一些操作比如包含了修改欄位A記錄,中間過程可能會出現有相同兩條記錄,但是事務的最後只會保證一條,事務要麼執行成功要麼全部回滾到原始狀態,這就是一致性。這個一致性跟上面的原子性有點讓人混淆,個人理解原子性強調的是事務不可分割不可中斷,一致性強調的是資料內容的一致性,偏向角度有所不同。


❑ 隔離性(isolation),隔離性是指多個不同事務在執行過程中是隔離的,互補干擾,比如多個事務對資料庫表格test進行讀取修改,不同事務之間是隔離的,不會看到其他事務的操作中間狀態或者結果。這裡還會引申出不同的隔離級別,下面會詳細描述。


❑ 永續性(durability),永續性這個最好理解的,就是資料一旦成功修改後就被永久儲存起來,不會出現丟失等情況(這個比較理想狀態的要求了)。


事務分類


從事務理論的角度來說, 可以把事務分為以下幾種型別:

❑ 扁平事務(Flat Transactions)
扁平事務(Flat Transaction)是事務型別中最簡單的一種,但在實際生產環境中, 這可能是使用最為頻繁的事務。在扁平事務中,所有操作都處於同一層次,其由BEGIN WORK開始,由COMMIT WORK或ROLLBACK WORK結束,其間的操作是原子的,要麼都執行,要麼都回滾。因此扁平事務是應用程式成為原子操作的基本組成模組。扁平事務的主要限制是不能提交或者回滾事務的某一部分,或分幾個步驟提交。

❑ 帶有儲存點的扁平事務(Flat Transactions with Savepoints)
帶有儲存點的扁平事務(Flat Transactions with Savepoint),除了支援扁平事務支援的操作外,允許在事務執行過程中回滾到同一事務中較早的一個狀態。

這是因為某些事務可能在執行過程中出現的錯誤並不會導致所有的操作都無效,放棄整個事務不合乎要求,開銷也太大。儲存點(Savepoint)用來通知系統應該記住事務當前的狀態,以便當之後發生錯誤時,事務能回到儲存點當時的狀態。


對於扁平的事務來說,其隱式地設定了一個儲存點。然而在整個事務中,只有這一個儲存點,因此,回滾只能回滾到事務開始時的狀態。儲存點用SAVE WORK函式來建立,通知系統記錄當前的處理狀態。當出現問題時,儲存點能用作內部的重啟動點,根據應用邏輯,決定是回到最近一個儲存點還是其他更早的儲存點。

❑ 鏈事務(Chained Transactions)
鏈事務(Chained Transaction)可視為儲存點模式的一種變種。帶有儲存點的扁平 事務,當發生系統崩潰時,所有的儲存點都將消失,因為其儲存點是易失的(volatile),而非持久的(persistent)。這意味著當進行恢復時,事務需要從開始處重新執行,而不能從最近的一個儲存點繼續執行。

鏈事務的思想是:在提交一個事務時,釋放不需要的資料物件,將必要的處理上下文隱式地傳給下一個要開始的事務。注意,提交事務操作和開始下一個事務操作將合併為一個原子操作。這意味著下一個事務將看到上一個事務的結果,就好像在一個事務中進行的一樣。

❑ 巢狀事務(Nested Transactions)
巢狀事務(Nested Transaction)是一個層次結構框架。由一個頂層事務(top- level transaction)控制著各個層次的事務。頂層事務之下巢狀的事務被稱為子事務(subtransaction),其控制每一個區域性的變換。

❑ 分散式事務(Distributed Transactions)
分散式事務(Distributed Transactions)通常是一個在分散式環境下執行的扁平事務,因此需要根據資料所在位置訪問網路中的不同節點。

對於InnoDB儲存引擎來說,其支援扁平事務、帶有儲存點的事務、鏈事務、分散式事務。對於巢狀事務,其並不原生支援,因此,對有並行事務需求的使用者來說,MySQL資料庫或InnoDB儲存引擎就顯得無能為力了。然而使用者仍可以通過帶有儲存點的事務來模擬序列的巢狀事務。


事務的實現


事務隔離性由第6章講述的鎖來實現。原子性、一致性、永續性 通過資料庫的redo log 和 undo log 來完成。redo log 稱為重做日誌,用來保證事務的原子性和永續性。undo log 用來保證事務的一致性。

有的DBA或許會認為 undo 是 redo 的逆過程,其實不然。redo 和 undo 的作用都可以視為是一種恢復操作,redo 恢復提交事務修改的頁操作,而 undo 回滾行記錄到 某個特定版本。因此兩者記錄的內容不同, redo 通常是物理日誌,記錄的是頁的物理修改操作。undo 是邏輯日誌,根據每行記錄進行記錄。

redo

重做日誌用來實現事務的永續性,即事務ACID中的D。其由兩部分組成:
一是記憶體中的重做日誌緩衝(redo log buffer),其是易失的;
二是重做日誌檔案(redo log file),其是持久的。

InnoDB 是事務的儲存引擎,其通過 Force Log at Commit 機制實現事務的永續性,即當事務提交(COMMIT)時,必須先將該事務的所有日誌寫入到重做日誌檔案進行持久化,待事務的 COMMIT 操作完成才算完成。

這裡的日誌是指重做日誌,在InnoDB 儲存引擎中,由兩部分組成,即 redo log 和 undo log。redo log 用來 保證事務的永續性, undo log 用來幫助事務回滾及 MVCC 的功能。redo log 基本上都是順序寫的,在資料庫執行時不需要對 redo log 的檔案進行讀取操作。而undo log 是需要進行隨機讀寫的。

為了確保每次日誌都寫入重做日誌檔案,在每次將重做日誌緩衝寫入重做日誌檔案後, InnoDB 儲存引擎都需要呼叫一次fsync操作。由於重做日誌檔案開啟並沒有使用 O_ DIRECT 選項,因此重做日誌緩衝先寫入檔案系統快取。為了確保重做日誌寫入磁碟, 必須進行一次 fsync 操作。 由於 fsync 的效率取決於磁碟的效能,因此磁碟的效能決定了事務提交的效能,也就是資料庫的效能。

undo

重做日誌記錄了事務的行為,可以很好地通過其對頁進行“重做”操作。但是事務有時還需要進行回滾操作,這時就需要undo。因此在對資料庫進行修改時,InnoDB儲存引擎不但會產生redo,還會產生一定量的undo。這樣如果使用者執行的事務或語句由於某種原因失敗了,又或者使用者用一條ROLLBACK語句請求回滾,就可以利用這些undo資訊將資料回滾到修改之前的樣子。

使用者通常對undo有這樣的誤解:undo用於將資料庫物理地恢復到執行語句或事務之前的樣子——但事實並非如此。undo是邏輯日誌,因此只是將資料庫邏輯地恢復到原來的樣子。所有修改都被邏輯地取消了,但是資料結構和頁本身在回滾之後可能大不相同。

這是因為在多使用者併發系統中,可能會有數十、數百甚至數千個併發事務。資料庫的主要任務就是協調對資料記錄的併發訪問。比如,一個事務在修改當前一個頁中某幾條記錄,同時還有別的事務在對同一個頁中另幾條記錄進行修改。因此,不能將一個頁回滾到事務開始的樣子,因為這樣會影響其他事務正在進行的工作。

purge

delete和update操作可能並不直接刪除原有的資料。例如,對上一小節所產生的表t執行如下的SQL語句:
DELETE FROM t WHEREa=1;
表t上列a有聚集索引,列b上有輔助索引。對於上述的delete操作,通過前面關於undolog的介紹已經知道僅是將主鍵列等於1的記錄deleteflag設定為1,記錄並沒有被刪除,即記錄還是存在於B+樹中。其次,對輔助索引上a等於1,b等於1的記錄同樣沒有做任何處理,甚至沒有產生undolog。而真正刪除這行記錄的操作其實被“延時”了,最終在purge操作中完成。

purge用於最終完成delete和update操作。這樣設計是因為InnoDB儲存引擎支援MVCC,所以記錄不能在事務提交時立即進行處理。這時其他事物可能正在引用這行,故InnoDB儲存引擎需要儲存記錄之前的版本。而是否可以刪除該條記錄通過purge來進行判斷。若該行記錄已不被任何其他事務引用,那麼就可以進行真正的delete操作。可見,purge操作是清理之前的delete和update操作,將上述操作“最終”完成。

group commit

若事務為非只讀事務,則每次事務提交時需要進行一次fsync操作,以此保證重做日誌都已經寫入磁碟。當資料庫發生宕機時,可以通過重做日誌進行恢復。雖然固態硬碟的出現提高了磁碟的效能,然而磁碟的fsync效能是有限的。為了提高磁碟fsync的效率,當前資料庫都提供了groupcommit的功能,即一次fsync可以重新整理確保多個事務日誌被寫入檔案。


事務的隔離級別


SQL 標準定義的四個隔離級別為:
❑ READ UNCOMMITTED
從名字可以猜想得到,可以讀到其他事務未commit的資料,髒讀。

❑ READ COMMITTED
參考上面的,這個的意思就是隻能讀到已經提交的資料,這種級別不會出現髒讀。單這種級別還不能避免一種情況叫“不可重複讀”,因為這種級別是可以讀到其他事務已經提交的資料,所以會出現時間T1(這個時候其他事務未提交)讀取到的資料,時間T2(這個時候有其他事務提交)再去讀取同樣的資料,會出現兩份不一樣的結果,就是所謂的“不可重複讀”。

❑ REPEATABLE READ
這個是mysql預設的隔離級別,這個級別解決了上面兩種級別所存在的問題:髒讀和不可重複讀。但是這種級別又不能解決一種現象稱之為“幻讀”。大概就是說在同一個事務裡面前後兩次查詢一樣的條件出來的結果不一樣,這個跟不可重複讀有點類似,經過網路的大量搜尋,幻讀與不可重複讀的區別是:不可重複讀一般是指由其他事務的update、delete造成,而幻讀一般由其他事務的insert造成。

❑ SERIALIZABLE
序列級別,也比較好理解,這種級別下則是通過鎖解決了上面所描述的三種問題。

不同的隔離級別有各自對資料讀寫的影響,主要有以下三種:
1.髒讀(dirty read):一個事務可以讀取另一個尚未提交事務的修改資料。

2.非重複讀(nonrepeatable read):在同一個事務中,同一個查詢在T1時間讀取某一行,在T2時間重新讀取這一行時候,這一行的資料已經發生修改,可能被更新了(update),也可能被刪除了(delete)。

3.幻像讀(phantom read):在同一事務中,同一查詢多次進行時候,由於其他插入操作(insert)的事務提交,導致每次返回不同的結果集。

下面是隔離級別和對應上面幾種影響個關係: