聊聊Mysql的事務、Spring中的@Transaction


事務是我們大部分程序員繞不過去的坎,事務到底是什麼?事務對我們有什麼好處?mysql的事務和spring的@Transaction是怎麼個關係?

什麼是事務?

事務(Transaction)是由一系列對系統中數據進行訪問與更新的操作所組成的一個程序執行邏輯單元(Unit)。
狹義上的事務特指數據庫事務。
—— 從Paxos到Zookeeper分佈式一致性原理與實踐 (倪超 著)

實際上,筆者通俗的認爲,一個事務就是一筆嚴謹公正、不能被外人影響的交易,無論交易成功失敗,被交易的資源都應該保持令人信服的事實狀態。
而當這筆被交易的資源是計算機數據庫中的數據時,我們就還要保證由於不可抗力計算機宕機導致的資源的不丟失。
於是根據定義,衍生出來的事務應該有以下四個特徵,也就是我們常說的ACID:

A : Atomic(原子性)

事務要麼成功,要麼失敗,沒有中間態。

但是筆者認爲,這裏的A相比於java或是計算機中鎖的原子性概念來說,要稍弱一些。鎖的原子性是一個不可切分的最小的操作,不會受到併發的影響。

而數據庫事務的A並沒有防止併發可能帶來問題。

C:Consistent(一致性)

筆者認爲數據庫這裏的的C應當與分佈式系統中的數據一致性/最終一致性區分開來,即:數據庫事務一致性不是爲了保證某兩個節點間的數據一致。

事務應確保數據庫的狀態從一個一致狀態轉變爲另一個一致狀態。一致狀態的含義是數據庫中的數據應滿足完整性約束. ——wiki百科

而所謂應滿足完整性約束,則指的是數據庫中的對於數據的約束,比如:

  • 非空約束,設置not null的字段不能爲空。
  • 唯一約束,設置unique的字段必須唯一。

等等。要讓數據在事務前和事務後,都保證滿足以上所描述的此類約束,就滿足了一致性的要求
記得見過這樣一個例子:

假設一個事務中,先刪除了主鍵id=1的數據,然後新增了一條id=1的數據,之後由於一些原因導致事務回滾,但是隻回滾了刪除操作。
結果導致數據庫存在兩條id=1的記錄,違反了主鍵唯一的性質。

I : Isolation(隔離性)

又是一個常見的考點,看到隔離性,我們就應該將四個隔離級別脫口而出。但是在此之前,讓我們先明白爲什麼需要隔離性?

回憶A(原子性)遺留的問題,我們能夠輕易想到,隔離性+原子性最終爲了保證數據的事務前後一致性防止出現併發的數據錯誤

四種隔離級別如下:

  1. Read Uncommited(讀未提交)
    等同於無事務,任何同時進行的事務都可以看到其他未提交事務的inset/update/delete後的數據。
    缺點:一切併發問題——髒讀、寫覆蓋、破壞前後數據一致性。

  2. Read Commited(讀已提交)
    大部分數據庫(除Mysql)默認的隔離級別。wiki對它的定義特別清晰。

    在提交讀(READ COMMITTED)級別中,基於鎖機制併發控制的DBMS需要對選定對象的寫鎖一直保持到事務結束,但是讀鎖在SELECT操作完成後馬上釋放(因此“不可重複讀”現象可能會發生)。和可重複讀隔離級別一樣,也不要求“範圍鎖”。 ——wiki百科

    通過定義,我們會發現,當前事務select的數據會隨着其他事務的提交而改變(即不可重複讀),這是該隔離級別的一個特性,如果使用Read Commited,要格外注意該特性,避免跳坑。

  3. REPEATABLE READS(可重複讀,mysql默認級別)
    看名字就能知道,我們是在Read Commited上加了一些限制,使得可以在當前事務中多次select都能得到一個結果(本來應該是這樣的)。

    在可重複讀(REPEATABLE READS)隔離級別中,基於鎖機制併發控制的DBMS需要對選定對象的讀鎖(read locks)和寫鎖(write locks)一直保持到事務結束,但不要求“範圍鎖”,因此可能會發生“幻影讀”。 ——wiki百科

    實際上,mysql可重複讀的實現與wiki描述不同,並非對選定對象加讀鎖,而是使用MVCC進行了數據的版本控制

    舉個例子:在A事務中對id=2的數據進行查詢,如果想保證在之後的查詢中該行都不變化,有兩個方法:對該數據行加鎖,其他事務不可修改對該數據多版本控制,本事務每次都只查看對應版本的數據

    第二種方式,就是MVCC做的事情,其實它的原理很簡單,就是對增刪改的操作進行版本記錄,相當於在一行數據後面加了個字段(版本號)。

    缺點:幻讀——可重複讀雖然可重複select==指定行的數據,但是對於範圍指定的select缺沒有限制。比如事務A中的兩次範圍查詢,很可能第一次查出來10條數據,第二次查出來11條。根據定義,我們明白,這個缺點產生的原因是可重複讀不要求範圍鎖。
    但是mysql的可重複讀級別不存在幻讀問題,它同樣通過MVCC解決了該問題。

  4. SERIALIZABLE(串行化)
    串行化將所有事務串行處理,這樣顯然不會存在任何併發問題,但是效率也將響應的變低。
    對於我們使用來說,Mysql的REPEATABLE READS已經解決了可預料的所有併發的問題,並且效率也並不比Read Commited低,所以一般來說,不需要更換隔離級別。

Durability 持久性

事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。 ——wiki百科

持久性是一個有意思的問題,即使系統故障也不會丟,那就只能通過高可用,主從節點間的強一致性來保證,即使這樣也存在主從全部一起掛掉的風險。筆者認爲,這個特性還是需要彈性考慮的。

使用事務有什麼好處?

通過上面的敘述,相信同學們已經有了一些想法,筆者自己做了一些思考,不一定對:

面向對象編程讓我們化萬物爲對象,關係型數據庫讓我們將萬物對象在計算機中連接起來,而這些萬物對象隨着時間推移、萬物對象自身的一些行爲方法會產生的一些規範的流程、我們所認爲的有規則有道理的流程,筆者認爲事務正是幫助我們描述了這些規範的流程,讓我們不需要關注數據在計算機中產生的一些問題,可以更好的只關注於做事

Spring中的@Transaction

Spring中的@Transaction是我們常見常用的一個註解,它是什麼?能做什麼?我們真的瞭解嗎。

@Transaction是什麼?

@Transaction將標註的方法納入事務管理,即:加上@Transaction註解就可以對該方法自動使用事務了。
一般情況下,基於Mysql數據源的程序中,@Transaction所使用的的就是我們上面所講述的Mysql的事務。

但是有一點我們需要注意,程序的方法不是隻有一個,是存在方法調用的。所以對於事務的使用就不得不先了解一下事務的傳播機制了。

@Transaction傳播機制

@Transaction提供了七種傳播機制,我們可以通過這些傳播機制來規定事務在方法調用中的傳播行爲。

筆者曾經寫過一個bug,回過頭來看很蠢,但是就屬於很典型的完全沒整明白傳播機制的問題。當時的代碼邏輯是這樣的:

A方法開啓了REQUIRED傳播,B方法也是默認的REQUIRED傳播,A方法中調用了B,(兩者屬於不同Class,可以確認@Transaction註解是生效的)。
在B方法中對某Exception進行拋出,A中捕獲了該異常,但是沒有拋出,而是打印日誌就結束。
結果執行報錯:Transaction rolled back because it has been marked as rollback-only
原理其實很簡單:由於B的傳播級別爲REQUIRED,所以B方法還是使用A傳遞過來的事務。
當B事務異常時事務被Spring標記爲Rollback-only表示該事務只能回滾了,結果A在捕獲到異常後沒有讓它回滾或繼續拋出讓Spring來處理回滾。
當A執行完方法,想要正常commit的時候,悲劇就發生了。

這個問題有兩種解決方法,一是在A的catch中的拋出異常或rollback;另一種則是修改B方法的隔離級別,讓A和B分別使用不同的事務。
當時我選擇了B(設置B的傳播機制爲REQUIRED_NEW),覺得更貼近邏輯。

不扯了,進入正題,七種事務傳播機制,前三種比較重要:

  1. REQUIRED。
    默認。該隔離級別會開啓一個新事務,但如果是被調用的方法(或者說若當前已存在一個事務),會直接使用傳遞過來的事務。

  2. REQUIRED_NEW。
    該隔離級別會開啓一個新事務,如果是被調用的方法,會新開啓一個事務。

  3. NESTED(嵌套)。
    這個有點兒東西,它的基本性質同REQUIRED_NEW,但是實現原理不同,REQUIRED_NEW是在spring級別又開啓了一個新事務,而NESTED則是mysql級別的嵌套事務

    展開嘮一下,Mysql中的事務其實是分多鐘的,有扁平事務、savepoint事務、嵌套事務、分佈式事務等。

    Mysql事務的分類
    扁平事務:即一般事務。
    savepoint事務:帶保存點的事務,我們可以在某個地方設置savepoint,然後再之後通過跳轉函數跳轉回該點 ,將數據恢復到該狀態。
    分佈式事務:mysql這裏一般指XA事務,是基於2PC協議的,以後再聊數據一致性協議。
    嵌套事務:事務可以開啓子事務,子事務還可以再分子事務,子事務的回滾不一定導致父事務回滾,但是父事務回滾一定導致子事務回滾。
    

    說到這裏,相信大家也看得出NESTED和REQUIRED_NEW的區別的REQUIRED_NEW開啓的子事務與父事務沒關係,父子誰回滾都不影響對方,但是NESTED裏面父事務回滾子事務也是要回滾的。

    其他的就是一些是否支持事務的傳播級別了。

  4. NOT_SUPPORTED 不支持事務並掛起任何存在的事務。

  5. SUPPORTS 支持事務但不一定非要有。

  6. NEVER 存在事務就拋異常。

  7. MANDATORY 必須有事務,否則拋異常。

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