漫談MySql中的事務

      最近一直在作訂單類的項目,使用了事務。咱們的數據庫選用的是MySql,存儲引擎選用innoDB,innoDB對事務有着良好的支持。這篇文章咱們一塊兒來扒一扒事務相關的知識。mysql

爲何要有事務?

        事務普遍的運用於訂單系統、銀行系統等多種場景。若是有如下一個場景:A用戶和B用戶是銀行的儲戶。如今A要給B轉帳500元。那麼須要作如下幾件事:sql

            1. 檢查A的帳戶餘額>500元;數據庫

            2. A帳戶扣除500元;併發

            3. B帳戶增長500元;分佈式

        正常的流程走下來,A帳戶扣了500,B帳戶加了500,皆大歡喜。那若是A帳戶扣了錢以後,系統出故障了呢?A白白損失了500,而B也沒有收到本該屬於他的500。以上的案例中,隱藏着一個前提條件:A扣錢和B加錢,要麼同時成功,要麼同時失敗。事務的需求就在於此。性能

事務是什麼?

        與其給事務定義,不如說一說事務的特性。衆所周知,事務須要知足ACID四個特性。atom

            1. A(atomicity) 原子性。一個事務的執行被視爲一個不可分割的最小單元。事務裏面的操做,要麼所有成功執行,要麼所有失敗回滾,不能夠只執行其中的一部分。spa

            2. C(consistency) 一致性。一個事務的執行不該該破壞數據庫的完整性約束。若是上述例子中第2個操做執行後系統崩潰,保證A和B的金錢總計是不會變的。3d

            3. I(isolation) 隔離性。一般來講,事務之間的行爲不該該互相影響。然而實際狀況中,事務相互影響的程度受到隔離級別的影響。文章後面會詳述。日誌

            4. D(durability) 持久性。事務提交以後,須要將提交的事務持久化到磁盤。即便系統崩潰,提交的數據也不該該丟失。

事務的四種隔離級別

        前文中提到,事務的隔離性受到隔離級別的影響。那麼事務的隔離級別是什麼呢?事務的隔離級別能夠認爲是事務的"自私"程度,它定義了事務之間的可見性。隔離級別分爲如下幾種:

          1.READ UNCOMMITTED(未提交讀)。在RU的隔離級別下,事務A對數據作的修改,即便沒有提交,對於事務B來講也是可見的,這種問題叫髒讀。這是隔離程度較低的一種隔離級別,在實際運用中會引發不少問題,所以通常不經常使用。

          2.READ COMMITTED(提交讀)。在RC的隔離級別下,不會出現髒讀的問題。事務A對數據作的修改,提交以後會對事務B可見,舉例,事務B開啓時讀到數據1,接下來事務A開啓,把這個數據改爲2,提交,B再次讀取這個數據,會讀到最新的數據2。在RC的隔離級別下,會出現不可重複讀的問題。這個隔離級別是許多數據庫的默認隔離級別。

          3.REPEATABLE READ(可重複讀)。在RR的隔離級別下,不會出現不可重複讀的問題。事務A對數據作的修改,提交以後,對於先於事務A開啓的事務是不可見的。舉例,事務B開啓時讀到數據1,接下來事務A開啓,把這個數據改爲2,提交,B再次讀取這個數據,仍然只能讀到1。在RR的隔離級別下,會出現幻讀的問題。幻讀的意思是,當某個事務在讀取某個範圍內的值的時候,另一個事務在這個範圍內插入了新記錄,那麼以前的事務再次讀取這個範圍的值,會讀取到新插入的數據。Mysql默認的隔離級別是RR,然而mysql的innoDB引擎間隙鎖成功解決了幻讀的問題。

          4.SERIALIZABLE(可串行化)。可串行化是最高的隔離級別。這種隔離級別強制要求全部事物串行執行,在這種隔離級別下,讀取的每行數據都加鎖,會致使大量的鎖徵用問題,性能最差。

       爲了幫助理解四種隔離級別,這裏舉個例子。如圖1,事務A和事務B前後開啓,並對數據1進行屢次更新。四個小人在不一樣的時刻開啓事務,可能看到數據1的哪些值呢?

           

                                                            圖1

        第一個小人,可能讀到1-20之間的任何一個。由於未提交讀的隔離級別下,其餘事務對數據的修改也是對當前事務可見的。第二個小人可能讀到1,10和20,他只能讀到其餘事務已經提交了的數據。第三個小人讀到的數據去決於自身事務開啓的時間點。在事務開啓時,讀到的是多少,那麼在事務提交以前讀到的值就是多少。第四個小人,只有在A end 到B start之間開啓,纔有可能讀到數據,而在事務A和事務B執行的期間是讀不到數據的。由於第四小人讀數據是須要加鎖的,事務A和B執行期間,會佔用數據的寫鎖,致使第四個小人等待鎖。

        圖2羅列了不一樣隔離級別所面對的問題。

        

                                                       圖2

        很顯然,隔離級別越高,它所帶來的資源消耗也就越大(鎖),所以它的併發性能越低。準確的說,在可串行化的隔離級別下,是沒有併發的。

                

                                                         圖3

MySql中的事務

        事務的實現是基於數據庫的存儲引擎。不一樣的存儲引擎對事務的支持程度不同。mysql中支持事務的存儲引擎有innoDB和NDB。innoDB是mysql默認的存儲引擎,默認的隔離級別是RR,而且在RR的隔離級別下更進一步,經過多版本併發控制(MVCC,Multiversion Concurrency Control )解決不可重複讀問題,加上間隙鎖(也就是併發控制)解決幻讀問題。所以innoDB的RR隔離級別其實實現了串行化級別的效果,並且保留了比較好的併發性能。

       事務的隔離性是經過鎖實現,而事務的原子性、一致性和持久性則是經過事務日誌實現。說到事務日誌,不得不說的就是redo和undo。

     1.redo log

       在innoDB的存儲引擎中,事務日誌經過重作(redo)日誌和innoDB存儲引擎的日誌緩衝(InnoDB Log Buffer)實現。事務開啓時,事務中的操做,都會先寫入存儲引擎的日誌緩衝中,在事務提交以前,這些緩衝的日誌都須要提早刷新到磁盤上持久化,這就是DBA們口中常說的「日誌先行」(Write-Ahead Logging)。當事務提交以後,在Buffer Pool中映射的數據文件纔會慢慢刷新到磁盤。此時若是數據庫崩潰或者宕機,那麼當系統重啓進行恢復時,就能夠根據redo log中記錄的日誌,把數據庫恢復到崩潰前的一個狀態。未完成的事務,能夠繼續提交,也能夠選擇回滾,這基於恢復的策略而定。

        在系統啓動的時候,就已經爲redo log分配了一塊連續的存儲空間,以順序追加的方式記錄Redo Log,經過順序IO來改善性能。全部的事務共享redo log的存儲空間,它們的Redo Log按語句的執行順序,依次交替的記錄在一塊兒。以下一個簡單示例:

        記錄1:<trx1, insert...>

        記錄2:<trx2, delete...>

        記錄3:<trx3, update...>

        記錄4:<trx1, update...>

        記錄5:<trx3, insert...>

      2.undo log

        undo log主要爲事務的回滾服務。在事務執行的過程當中,除了記錄redo log,還會記錄必定量的undo log。undo log記錄了數據在每一個操做前的狀態,若是事務執行過程當中須要回滾,就能夠根據undo log進行回滾操做。單個事務的回滾,只會回滾當前事務作的操做,並不會影響到其餘的事務作的操做。

        如下是undo+redo事務的簡化過程

        假設有2個數值,分別爲A和B,值爲1,2

        1. start transaction;

        2. 記錄 A=1 到undo log;

        3. update A = 3;

        4. 記錄 A=3 到redo log;

        5. 記錄 B=2 到undo log;

        6. update B = 4;

        7. 記錄B = 4 到redo log;

        8. 將redo log刷新到磁盤

        9. commit

        在1-8的任意一步系統宕機,事務未提交,該事務就不會對磁盤上的數據作任何影響。若是在8-9之間宕機,恢復以後能夠選擇回滾,也能夠選擇繼續完成事務提交,由於此時redo log已經持久化。若在9以後系統宕機,內存映射中變動的數據還來不及刷回磁盤,那麼系統恢復以後,能夠根據redo log把數據刷回磁盤。

        因此,redo log其實保障的是事務的持久性和一致性,而undo log則保障了事務的原子性。

分佈式事務

        分佈式事務的實現方式有不少,既能夠採用innoDB提供的原生的事務支持,也能夠採用消息隊列來實現分佈式事務的最終一致性。這裏咱們主要聊一下innoDB對分佈式事務的支持。

        

        如圖,mysql的分佈式事務模型。模型中分三塊:應用程序(AP)、資源管理器(RM)、事務管理器(TM)。

        應用程序定義了事務的邊界,指定須要作哪些事務;

        資源管理器提供了訪問事務的方法,一般一個數據庫就是一個資源管理器;

        事務管理器協調參與了全局事務中的各個事務。

        分佈式事務採用兩段式提交(two-phase commit)的方式。第一階段全部的事務節點開始準備,告訴事務管理器ready。第二階段事務管理器告訴每一個節點是commit仍是rollback。若是有一個節點失敗,就須要全局的節點所有rollback,以此保障事務的原子性。

總結

        何時須要使用事務呢?我想,只要業務中須要知足ACID的場景,都須要事務的支持。尤爲在訂單系統、銀行系統中,事務是不可或缺的。這篇文章主要介紹了事務的特性,以及mysql innoDB對事務的支持。事務相關的知識遠不止文中所說,本文僅做拋磚引玉,不足之處還望讀者多多見諒。

 

參考文獻:

 《高性能mysql第三版》

 《mysql技術內幕 innoDB存儲引擎》

 

做者: mayday芋頭
本博客中未標明轉載的文章歸做者mayday芋頭和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索