MySQL事務內幕與ACID

MySQL的事務實現嚴格遵循ACID特性,即原子性(atomicity),一致性(consistency),隔離性(isolation),持久性(durability)。爲了避免一上來就陷入對ACID的概念理解,我們直接先談MySQL事務實現機制。

            MySQL提供了兩種事務型的存儲引擎:InnoDB和NDB Cluster。另外還有一些第三方存儲引擎也支持事務,比較知名的包括XtraDB和PBXT。下面以InnoDB來說明。

            MySQL會最大程度的使用緩存機制來提高數據庫的訪問效率,但是萬一數據庫發生斷電,因爲緩存的數據沒有寫入磁盤,導致緩存在內存中的數據丟失而導致數據不一致怎麼辦?

            InnoDB主要是通過事務日誌實現ACID特性,事務日誌可以幫助提高事務的效率。使用事務日誌,存儲引擎在修改表的數據時只需要修改其內存拷貝,再把該修改行爲記錄到持久在硬盤上的事務日誌中,而不用每次都將修改的數據本身持久到磁盤。下載

           事務日誌採用追加的方式,因此寫日誌的操作是磁盤上一小塊區域的順序I/O,而不像隨機I/O需要在磁盤的多個地方移動磁頭。所以事務日誌的方式相對來說要快得多。事務日誌持久以後,內存中被修改的數據在後臺可以慢慢的刷回磁盤。目前大多數存儲引擎是這樣實現的,我們稱之爲預寫式日誌,即修改數據需要寫兩次磁盤。

           事務日誌包括重做日誌redo和回滾日誌undo,Redo記錄的是已經全部完成的事務,就是執行了commit的事務,記錄文件是ib_logfile0 ib_logfile1。Undo記錄的是已部分完成並且寫入硬盤的未完成的事務,默認情況下回滾日誌是記錄在表空間中的(共享表空間或者獨享表空間)。

           一般情況下,mysql在崩潰之後,重啓服務,innodb通過回滾日誌undo將所有已完成並寫入磁盤的未完成事務進行rollback,然後redo中的事務全部重新執行一遍即可恢復數據,但是隨着redo的量增加,每次從redo的第一條開始恢復就會浪費長的時間,所以引入了checkpoint機制。

          一般業務運行過程中,當業務需要對某張表的某行數據進行修改的時候,innodb會先將該數據從磁盤讀取到緩存中去,然後在緩存中對這條數據進行修改,這樣緩存中的數據就和磁盤的數據不一致了,這個時候緩存中的數據就稱爲dirty page,只有當髒頁統一刷新到磁盤中才會是clean page。下載

          Checkpoint:如果在某個時間點,髒頁的數據被刷新到了磁盤,系統就把這個刷新的時間點記錄到redo log的結尾位置,在進行恢復數據的時候,checkpoint時間點之前的數據就不需要進行恢復了,可以縮短時間。

 

Innodb_log_buffer_size 重做日誌緩存大小

Innodb_log_file_size redo log文件大小  文件越大 數據恢復的時間越長

Innodb_log_file_group redo log文件數量 默認是2個 ib_logfile0 ib_logfile1

 

           事務日誌的的機制實際上是滿足的事務的原子性和持久性,即要麼都成功,要麼都失敗。而談到事務的一致性和隔離性,就要談到“鎖”了。

           InnoDB採用的是兩階段鎖定協議。在事務執行過程中,隨時都可以執行鎖定,鎖只有在執行COMMIT或ROLLBACK時纔會釋放,並且所有的鎖同一時刻釋放。InnoDB會根據隔離級別在需要的時候自動加鎖。

           MySQL的大多數事務型存儲引擎實現的都不是簡單的行級鎖。基於提升併發性能的考慮,它們一般都同時實現了多版本併發控制(MVCC)。MVCC實現了非阻塞的讀操作,寫操作也只鎖定必要的行。MVCC的實現,是通過保存數據在某個時間點的快照來實現的。對於InnoDB,是通過在每行記錄後面保存兩個隱藏的列來實現的。這兩個列,一個保存了行的創建時間,一個保存了行的過期時間(或刪除時間)。當然存儲的並不是實際時間值,而是系統版本號。每開始一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作爲事務的版本號,用來和查詢到的每行記錄的版本號進行比較。下面看一下在MySQL默認隔離級別REPEATABLE READ下,MVCC具體是如何操作的。下載

  SELECT

  InnoDB會根據以下兩個條件檢查每行記錄:

  a. InnoDB只查找版本早於當前事務版本的數據行(也就是,行的系統版本號<=事務的系統版本好),這樣可     以確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或修改過的。

  b. 行的刪除版本要麼未定義,要麼大於當前事務版本號。這樣可以確保事務讀取到的行,在事務開始之前未被   刪除。

  只有符合上述兩個條件的記錄,才能返回作爲查詢結果。

  INSERT

  InnoDB爲新插入的每一行保存當前系統版本號作爲行版本號。

  DELETE

  InnoDB爲刪除的每一行保存當前系統版本號作爲刪除標識。

  UPDATE

  InnoDB爲插入一行新記錄,保存當前系統版本號作爲行版本號,同時保存當前系統版本號到原來的行作爲行     刪除標識。

  保存這兩個額外系統版本號,使大多數讀操作都可以不用加鎖。這樣讀操作簡單而性能好,也能保證讀到符合標準的行。不足之處是需要額外的存儲空間,需要做更多的行檢查工作,以及一些額外的維護工作。另外,MVCC只在REPEATABLE READ和READ COMMITED兩個隔離級別下工作。其他兩個隔離級別都和MVCC不兼容,因爲READ UNCOMMITED縱是讀取到最新的數據行,而不是符合當前事務版本的數據行,而SERIALIZABLE則會對所有讀取的行都加鎖。

  好了,相信你對MySQL事務實現機制有些瞭解了,下面摘抄兩段知乎上關於ACID的理解。下載

--------------

講道理

定義:數據庫一致性(Database Consistency)是指事務執行的結果必須是使數據庫從一個一致性狀態變到另一個一致性狀態。

數據庫狀態如何變化?每一次數據變更就會導致數據庫的狀態遷移。如果數據庫的初始狀態是C0,第一次事務T1的提交就會導致系統生成一個SYSTEM CHANGE NUMBER(SCN),這是數據庫狀態從C0轉變成C1。執行第二個事務T2的時候數據庫狀態從C1變成C2,以此類推,執行第Tn次事務的時候數據庫狀態由C(n-1)變成Cn。

定義一致性主要有2個方面,一致讀和一致寫。

一致寫:事務執行的數據變更只能基於上一個一致的狀態,且只能體現在一個狀態中。T(n)的變更結果只能基於C(n-1),C(n-2), ...C(1)狀態,且只能體現在C(n)狀態中。也就是說,一個狀態只能有一個事務變更數據,不允許有2個或者2個以上事務在一個狀態中變更數據。至於具體一致寫基於哪個狀態,需要判斷T(n)事務是否和T(n-1),T(n-2),...T(1)有依賴關係。

一致讀:事務讀取數據只能從一個狀態中讀取,不能從2個或者2個以上狀態讀取。也就是T(n)只能從C(n-1),C(n-2)... C(1)中的一個狀態讀取數據,不能一部分數據讀取自C(n-1),而另一部分數據讀取自C(n-2)。

擺事實

一致寫:
定義100個事務T(1)...T(100)實現相同的邏輯 update table set i=i+1,i的初始值是0,那麼併發執行這100個事務之後i的值是多少?可能很容易想到是100。那麼怎麼從一致性角度去理解呢?

數據庫隨機調度到T(50)執行,此時數據庫狀態是C(0),而其它事務都和T(50)有依賴關係,根據寫一致性原理,其它事務必須等到T(50)執行完畢後數據庫狀態變爲C(1)纔可以執行。因此數據庫利用鎖機制阻塞其它事務的執行。直到T(50)執行完畢,數據庫狀態從C(0)遷移到C(1)。數據庫喚醒其它事務後隨機調度到T(89)執行,以此類推直到所有事務調度執行完畢,數據庫狀態最終變爲C(100)。下載
一致讀:
還是上面的例子,假設T(1)...T(100)順序執行,在不同的時機執行select i from table,我們看到i的值是什麼?
1. T(1)的執行過程中。數據庫狀態尚未遷移,讀到的i=0

2. T(1)執行完畢,T(2)的執行過程中,數據庫狀態遷移至C(1),讀到的i=1 

 

---------------


講數據庫事務一致性怎麼能不提數據庫的ACID特性。
首先介紹事務,什麼是事務,事務就是DBMS當中用戶程序的任何一次執行,事務室DBMS能看到的基本修改單元。

事務是指對系統進行的一組操作,爲了保證系統的完整性,事務需要具有ACID特性,具體如下:
1. 原子性(Atomic)
一個事務包含多個操作,這些操作要麼全部執行,要麼全都不執行。實現事務的原子性,要支持回滾操作,在某個操作失敗後,回滾到事務執行之前的狀態。
回滾實際上是一個比較高層抽象的概念,大多數DB在實現事務時,是在事務操作的數據快照上進行的(比如,MVCC),並不修改實際的數據,如果有錯並不會提交,所以很自然的支持回滾。
而在其他支持簡單事務的系統中,不會在快照上更新,而直接操作實際數據。可以先預演一邊所有要執行的操作,如果失敗則這些操作不會被執行,通過這種方式很簡單的實現了原子性。下載


2. 一致性(Consistency)
一致性是指事務使得系統從一個一致的狀態轉換到另一個一致狀態。事務的一致性決定了一個系統設計和實現的複雜度。事務可以不同程度的一致性:
強一致性:讀操作可以立即讀到提交的更新操作。
弱一致性:提交的更新操作,不一定立即會被讀操作讀到,此種情況會存在一個不一致窗口,指的是讀操作可以讀到最新值的一段時間。
最終一致性:是弱一致性的特例。事務更新一份數據,最終一致性保證在沒有其他事務更新同樣的值的話,最終所有的事務都會讀到之前事務更新的最新值。如果沒有錯誤發生,不一致窗口的大小依賴於:通信延遲,系統負載等。
其他一致性變體還有:
單調一致性:如果一個進程已經讀到一個值,那麼後續不會讀到更早的值。
會話一致性:保證客戶端和服務器交互的會話過程中,讀操作可以讀到更新操作後的最新值。

3. 隔離性(Isolation)下載

併發事務之間互相影響的程度,比如一個事務會不會讀取到另一個未提交的事務修改的數據。在事務併發操作時,可能出現的問題有:
髒讀:事務A修改了一個數據,但未提交,事務B讀到了事務A未提交的更新結果,如果事務A提交失敗,事務B讀到的就是髒數據。
不可重複讀:在同一個事務中,對於同一份數據讀取到的結果不一致。比如,事務B在事務A提交前讀到的結果,和提交後讀到的結果可能不同。不可重複讀出現的原因就是事務併發修改記錄,要避免這種情況,最簡單的方法就是對要修改的記錄加鎖,這回導致鎖競爭加劇,影響性能。另一種方法是通過MVCC可以在無鎖的情況下,避免不可重複讀。
幻讀:在同一個事務中,同一個查詢多次返回的結果不一致。事務A新增了一條記錄,事務B在事務A提交前後各執行了一次查詢操作,發現後一次比前一次多了一條記錄。幻讀是由於併發事務增加記錄導致的,這個不能像不可重複讀通過記錄加鎖解決,因爲對於新增的記錄根本無法加鎖。需要將事務串行化,才能避免幻讀。
事務的隔離級別從低到高有:
Read Uncommitted:最低的隔離級別,什麼都不需要做,一個事務可以讀到另一個事務未提交的結果。所有的併發事務問題都會發生。
Read Committed:只有在事務提交後,其更新結果纔會被其他事務看見。可以解決髒讀問題。
Repeated Read:在一個事務中,對於同一份數據的讀取結果總是相同的,無論是否有其他事務對這份數據進行操作,以及這個事務是否提交。可以解決髒讀、不可重複讀。
Serialization:事務串行化執行,隔離級別最高,犧牲了系統的併發性。可以解決併發事務的所有問題。
通常,在工程實踐中,爲了性能的考慮會對隔離性進行折中。

4. 持久性(Durability)
事務提交後,對系統的影響是永久的。


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