文章為《MySQL技術內幕:InnoDB儲存引擎(第二版)》、掘金小冊《MySQL是怎樣執行的:從根兒上理解MySQL》 的筆記
事務概述
在現實生活中,大家都有過以下經歷(也是事務的經典場景之一):
- 最近沒錢了,需要去銀行取些錢用,然後就去ATM機取錢
- 在ATM機取 100 塊錢
- 銀行程式會在將銀行賬戶相應的減少 100 塊錢
- 最終取到錢
以上步驟是在理想的情況下進行的,假如在第二步驟時ATM機突然斷電/壞了呢,錢沒拿到,而賬戶裡卻少了 100 塊,是不是特別冤枉,再或者,假如在第三步驟的時候,銀行伺服器突然當機了,使用者拿到了錢,而賬戶資金卻沒有相應的減少,銀行會相應的虧損 100 塊。
在這樣的情況下雙方資金都得不到保障,這樣對雙方都是不利的。
事務的出現就是為了保證以上情景會正常運作,事務會把資料庫的一種一致狀態轉換為另外一種一致狀態,在資料庫提交工作時,可以確保要麼所有修改都儲存,要麼所以修改都不會儲存,在上述場景中,假如ATM機突然斷電了或者銀行伺服器當機了,不論發生什麼異常場景事務都會直接回滾,不會將所做的修改提交
ACID性質
其實我們只是想讓資料庫操作完全符合我們正常邏輯的狀態轉換而已,在InnoDB儲存引擎中的事務也完全符合ACID的特性。
原子性(Atomicity)
眾所周知原子是現實中物理最小的單位,不可再分割,在資料庫中,原子性指的是整個資料庫事務是不可分割單位,只有事務執行成功才算整個事務執行成功,若其中任何一條sql語句執行失敗則整個事務都執行失敗,執行成功的sql也將撤回,資料庫狀態應該退回事務開始之前的狀態。
一致性(Consistency)
現實生活中有著許許多多的約束,比如我們的身份證號碼不能重複,性別只有男女(正常來說),紅綠燈只有三種顏色等等,那麼在資料庫中同樣存在著約束,比如比一張表中有一列的索引為唯一索引(unique index)那麼這一列不能有重複行資料,當然更多的一致性需求還是需要靠寫業務程式碼的人來保證該一致性。最終一致性保證的是關注資料的可見性,中間狀態的資料對外不可見,只有最初狀態和最終狀態的資料對外可見的。
隔離性(Isolation)
隔離性還有另外一個稱呼“併發控制、可序列化、鎖等等”現實世界中的兩次狀態轉化應該是互不影響的,還是取錢場景舉例,假如同時在兩臺ATM機取錢,只扣了一臺ATM所取出的錢,這樣是不合理的對吧,所以事務的隔離性要求每個讀寫事務的物件對其他事務操作的物件能相互獨立分離,即事務提交前對其他事務不可見,通常用鎖來實現。
永久性(Durability)
事務一旦提交了就不能反悔了,因為一旦事務提交那麼結果就是永久性的,即使資料庫發生當機,資料也能恢復。
事務分類
- 扁平事務:最簡單也是實際使用最頻繁的一種事務,由
begin
開始,commit work
或者rollback
結束,期間的操作都是原子性操作,要麼執行,要麼回滾 - 帶有儲存點的扁平事務
- 除了支援扁平事務支援的操作外,允許在事務執行過程中回滾到同一事務中較早的一個狀態
- 儲存點用來通知系統應該記住事務當前的狀態,一旦事務過程中發生錯誤,事務能回到儲存點當時的狀態
- 鏈事務
- 可視為儲存點模式的一種變種,帶有儲存點的扁平事務,當發生系統崩潰時,所有儲存點都將消失,因為儲存點是易失的而非持久的
- 這意味著當進行恢復時,事務需要從開始處重新執行,而不能從最近的一個儲存點繼續執行
- 巢狀事務:由一個頂層事務控制著各個層次的事務,頂層事務之下巢狀的事務稱為子事務,其控制著每個區域性的變換
- 分散式事務:通常是一個在分散式環境下執行的扁平事務,因此需要根據資料所在的位置訪問網路中的不同節點
事務實現
原子性、一致性、永續性通過資料庫的 redo log
和 undo log
來完成,redo log
稱作為重做日誌,用來保證事務的原子性和永續性, undo log
用來保證事務的一致性。有些人可能會認為 undo log
是 redo log
的逆過程,其實不然,redo
和 undo
的作用都可以視為是一種恢復操作,redo
恢復提交事務修改的頁操作,而undo回滾記錄到某個特定的版本,因此兩者記錄的內容不同,redo
通常是物理日誌,記錄的是頁的物理修改操作,undo
是邏輯日誌,根據每行記錄進行記錄。
redo log
上面也提到了 redo log
就是為了事務的永續性(D),在事務提交後會將修改內容重新整理至磁碟中,即使資料庫當機,在重啟後會 redo log
記錄的修改內容重新整理至磁碟中去。
使用 redo log
的好處:
redo log
佔用空間小redo log
順序寫入磁碟:在執行事務過程中,沒執行一條 sql 語句就會產生若干條redo log
,這些日誌按照產生順序寫入磁碟的,也就是使用順序IO
log block
InnoDB儲存引擎中,重做日誌都是以512位元組進行儲存的,重做日誌快取、重做日誌檔案都是以塊(block)的方式進行儲存的,稱之為重做日誌塊(redo log block),每塊大小為512位元組
redo log 格式
由於InnoDB儲存引擎的儲存管理是基於頁的,故其重做日誌格式也是基於頁的,其頭部格式由三部分組成為:
- redo_log_type:重做日誌的型別
- space:表空間的ID
- page_no:頁的偏移量
undo log
redo 存放在重做日誌檔案中,與 redo 不同的是,undo 存放在資料庫內部的一個特殊段中,這個段稱為 undo 段,重做日誌記錄了事務的行為,可以很好的通過其對頁進行“重做”操作,但是事務有時還是需要進行回滾操作,那麼這時就需要 undo ,除了回滾操作,undo 的另外一個作用是 MVCC(多版本併發控制),即InnoDB儲存引擎的MVCC的實現是由 undo 來完成的。
undo儲存管理
InnoDB儲存引擎有 rollback segment,每個回滾段種記錄了 1024 個 undo log segment,而在每個 undo log segment 段種進行 undo 頁的申請。
undo log 格式
- insert undo log:insert 操作產生的undo log ,在事務提交後就刪除
- update undo log:delete 和 update 操作產生的 undo log,該undo log 可能需要提供MVCC機制,因此不能在事務提交時就刪除
purge
- purge 用於最終完成 update 和 delete 操作,來支援 MVCC
- 是否可以完全刪除由 purge 來判斷,若該行記錄已完全不被事務引用,那麼就進行真正的 delete 操作
group commit
若事務為非只讀事務,則每次事務提交時需要進行一次fsync操作,以此保證重做日誌都已經寫入磁碟,為了提高磁碟fsync的效率,當前資料庫提供了 group commit 的功能,即一次fsync可以重新整理確保多個事務日誌被寫入檔案。
對於InnoDB來說,提交事務的兩個階段:
- 修改記憶體中事務對應的資訊,並且將日誌寫入重做日誌緩衝
- 呼叫fsync將確保日誌都從重做日誌緩衝寫入磁碟
事務控制語句
- start transaction、begin:開始顯示事務
- commit:提交事務
- rollback:回滾事務
- savepoint identifier:允許在事務中建立一個儲存點,一個事務可以由多個儲存點
- release savepoint identifier:刪除一個事務儲存點,當沒有一個儲存點,執行該條語句時會丟擲一個異常
- rollback to[savepoint] identifier:這個語句與savepoint命令一起使用,可以把事務回滾到標記點,而不回滾在此標點之前的任何工作
- set transation:設定事務的隔離級別
使用事務時不好的習慣
- 在迴圈中提交
- 使用自動提交
- 使用自動回滾
長事務
- 執行時間較長的事務
- 對於長事務的問題,有時可以通過轉換為小批量的事務進行處理,當事務發生錯誤時,只需要回滾一部分資料,然後接著上次完成的事務繼續執行