1. 程式人生 > >MySQL技術內幕 InnoDB儲存引擎:事務

MySQL技術內幕 InnoDB儲存引擎:事務

一、認識事務

InnoDB儲存引擎中的事務完全符合ACID的特性。ACID是以下4個詞的縮寫:

  • 原子性(Atomicity):一個事務必須被視為一個不可分割的最小工作單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾。
  • 一致性(consistency):資料庫總是從一個一致性的狀態轉換到另一個一致性的狀態。(其實原子性和隔離性間接的保證了一致性)
  • 隔離性(isolation):通常來說,一個事務所做的修改在最終提交以前,對其他事務是不可見的。
  • 永續性(durability):一旦事務提交,則其所做的修改就會永久儲存到資料庫中。

而我們最常說的隔離性其實有對應的隔離級別,MySQL規定的隔離級別有4種,分別是:

  • READ UNCOMMITTED(讀未提交):在此級別裡,事務的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的資料,也就是會產生髒讀,在實際應用中一般很少使用。
  • READ COMMITTED(讀已提交):大多數資料庫系統的預設隔離級別都是它,但是MySQL不是。它能夠避免髒讀問題,但是在一個事務裡對同一條資料的多次查詢可能會得到不同的結果,也就是會產生不可重複讀問題。
  • REPEATABLE READ(可重複讀):該隔離級別是MySQL預設的隔離級別,看名字就知道它能夠防止不可重複讀問題,但是在一個事務裡對一段資料的多次讀取可能會導致不同的結果,也就是會有幻讀的問題(注:這裡說的無法解決是MySQL定義層面,對於InnoDB引擎則完美的解決了幻讀的問題,如果你正在使用InnoDB引擎,可忽略)
  • SERIALIZABLE(可序列化):該隔離級別是級別最高的,它通過鎖來強制事務序列執行,避免了前面說的所有問題。在高併發下,可能導致大量的超時和鎖爭用問題。實際應用中也很少用到這個隔離級別,因為RR級別解決了所有問題。
    可以看到隔離級別裡最重要的只有兩個隔離級別:RC和RR。

二、事務的實現

事務隔離性由鎖來實現。原子性、一致性、永續性通過資料庫的redo log和undo log來完成。

redo log稱為重做日誌,用來保證事務的原子性和永續性。undo log用來保證事務的一致性。

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

1、redo

基本概念

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

InnoDB是事務的儲存引擎,其通過Force Log at Commit機制實現事務的永續性,即當事務提交時,必須先將該事務的所有日誌寫入到重做日誌檔案進行持久化,待事務的COMMIT操作完成才算完成。redo log基本上都是順序寫的,在資料庫執行時不需要對redo log的檔案進行讀取操作。而undo log是需要隨機讀寫的。

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

InnoDB允許使用者手工設定非持久化的情況發生,以此提高資料庫的效能。即當事務提交時,日誌不寫入重做日誌檔案,而是等待一個時間週期後再執行fsync操作。但宕機會丟失一部分資料。

引數innodb_flush_log_at_trx_commit用來控制重做日誌重新整理到磁碟的策略。預設值是1,表示事務提交時必須呼叫一次fsync操作, 0表示事務提交時不進行寫入重做日誌操作,這個操作僅在master thread中完成,而在master thread中每1s會進行一次重做日誌檔案的fsync操作。2表示事務提交時將重做日誌寫入重做日誌檔案,但僅寫入快取,不進行fsync操作。
在這裡插入圖片描述
在 MySQL資料庫中還葙一種二進位制日誌(binlog),其用來進行POINT-IN-TIME
(PIT)的恢復及主從複製(Replication)環境的建立。從表面上看其和重做日誌罪常相似,都是記錄了對幹資料庫操作的日誌。然而,從本質上來看,兩者有著非常大的不同。

首先,重做日誌是在InnoDB儲存引擎層產生,而二進位制日誌是在MySQL資料庫的上層產生的,並且二進位制日誌不僅僅針對於InnoDB儲存引擎,MySQL資料庫中的任何儲存引擎對於資料庫的更改都會產生二進位制日誌。

其次,兩種日誌記錄的內容形式不同。MySQL資料庫上層的二進位制日誌是一種邏輯日誌,其記錄的是對應的SQL語句。而InnoDB存引擎層面的重做日誌是物理格式日誌,其記錄的是對於每個頁的修改。

此外,兩種日誌記錄寫入磁碟的時間點不同,如圖7-6所示。二進位制日誌只在事務提交完成後進行一次寫入。而InnoDB儲存引擎的重做日誌在事務進行中不斷地被寫入,這表現為日誌並不是隨事務提交的順序進行寫入的。
在這裡插入圖片描述

從圖7-6中可以看到,二進位制日誌僅在事務提交時記錄,並且對於每一個事務,僅包含對應亊務的一個日誌。而對於InnoDB儲存引擎的重做日誌,由於其記錄的是物理操作日誌,因此每個事務對應多個日誌條目,並且事務的重做日誌寫人是併發的,並非在事務提交時寫人,故其茌檔案中記錄的順序並非是事務開始的順序。*T1、*T2、*T3表示的是事務提交時的日誌。

log block

在InnoDB儲存引擎中,重做日誌都是以512位元組進行儲存的。這意味著重做日誌快取、重做日誌檔案都是以塊(block)的方式進行儲存的,稱之為重做日誌塊(redo log block),每塊的大小為512位元組。

若—個頁中產生的重做日誌數置大幹512位元組,那麼需要分割為多個重做日誌塊進行儲存。此外,由於重做日誌塊的大小和磁碟扇區大小一樣,都是512位元組,因此重做日誌的寫入可以保證原子性,不需要doublewrite技術。

2、undo

基本概念

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

redo存放在重做日誌檔案中,與redo不同,undo存放在資料庫內部的一個特殊段(segment)中,這個段稱為undo段( undo segment)。undo段位於共享表空間內。可以通過py_innodb_page_info.py 工具來檢視當前共草表空間中undo的數量。

除了回滾操作,undo的另一個作用是 MVCC,即在InnoDB儲存引擎中 MVCC的實現是通過undo來完成。當用戶讀取一行記錄時,若該記錄已經被芄他事務佔用,當前事務可以通過undo讀取之前的行版本資訊,以此實現非鎖定讀取。

最後也是最為重要的一點是, undo log會產生redo !og,也就是 undo log的產生會伴隨著redo log的產生,這是因為undo log也需要永續性的保護。

此外,若為每一個事務分配一個單獨的undo頁會非常浪費儲存空間,特別是對於OLTP的應用型別。因為在事務提交時,可能並不能馬上釋放頁。假設某應用的刪除和更細操作的TPS (transaction per second)為1000,為每個事務分配一個undo頁,那麼一分鐘就需要1000*60個頁,大約需要的儲存空間為1GB。若每秒的purge頁的數量為20,這樣的設計對磁碟空間有著相當高的要求。因此,在InnoDB儲存引擎的設計中對undo頁可以進行重用。
具體來說,當事務提交時,首先將undo log放人鏈表中,然後判斷undo頁的使用空間是否小於3/4,若是則表示該undo頁可以被重用,之後新的undo log記錄在當前undo log的後面。由於存放undo log的列表是以記錄進行組織的,而undo頁可能存放著不同事務的undo log,因此purge操作需要涉及磁碟的離散讀取操作,是一個比較緩慢的過程。

3、purge

delete和update操作可能並不直接刪除原有的資料。例如,對上一小節所產生的表t執行如下的SQL語句:

DELETE FROM t WHERE a=1;

表 t上 列 a有聚集索引,列 b上有輔助索引。對於上述的 delete操作,通過前面關於undo log的介紹已經知道僅是將主鍵列等於1的記錄delete flag設定為1,記錄並沒有被刪除,即記錄還是存在於 B+樹中。其次,對輔助索引上 a等於1, b等於1的記錄同樣沒有做任何處理,甚至沒有產生undo log。而真正刪除這行記錄的操作其實被“延時”了,最終在purge操作中完成。

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

可見,purge操作是清理之前的delete和update操作,將上述操作“最終”完成。而實際執行的操作為delete操作,清理之前行記彔的版本。

本文整理自:《MySQL技術內幕 InnoDB儲存引擎

個人微信公眾號:
這裡寫圖片描述

作者:jiankunking 出處:http://blog.csdn.net/jiankunking