Transaction 也就是所謂的事務了,通俗理解就是一件事情。從小,父母就教育咱們,作事情要善始善終,不能半途而廢。�0�2事務也是這樣,不能作通常就不作了,要麼作完,要麼就不作。也就是說,事務必須是一個不可分割的總體,就像咱們在化學課裏學到的原子,原子是構成物質的最小單位。因而,人們就概括出事務的第一個特性:原子性(Atomicity)。我靠,一點都不神祕嘛。java
特別是在數據庫領域,事務是一個很是重要的概念,除了原子性之外,它還有一個極其重要的特性,那就是:一致性(Consistency)。也就是說,執行完數據庫操做後,數據不會被破壞。打個比方,若是從 A 帳戶轉帳到 B 帳戶,不可能由於 A 帳戶扣了錢,而 B 帳戶沒有加錢吧。若是出現了這類事情,您必定會很是氣憤,什麼 diao 銀行啊!git
當咱們編寫了一條 update 語句,提交到數據庫的一剎那間,有可能別人也提交了一條 delete 語句到數據庫中。也許咱們都是對同一條記錄進行操做,能夠想象,若是不稍加控制,就會出大麻煩來。咱們必須保證數據庫操做之間是「隔離」的(線程之間有時也要作到隔離),彼此之間沒有任何干擾。這就是:隔離性(Isolation)。github
要想真正的作到操做之間徹底沒有任何干擾是很難的,因而乎,天天上班打醬油的數據庫專家們,開始動腦筋了,「咱們要制定一個規範,讓各個數據庫廠商都支持咱們的規範!」,這個規範就是:事務隔離級別(Transaction Isolation Level)。能定義出這樣牛逼的規範真的挺不容易的,其實說白了就四個級別:web
- READ_UNCOMMITTED
- READ_COMMITTED
- REPEATABLE_READ
- SERIALIZABLE
千萬不要去翻譯,那只是一個代號而已。從上往下,級別愈來愈高,併發性愈來愈差,安全性愈來愈高,反之則反。sql
當咱們執行一條 insert 語句後,數據庫必需要保證有一條數據永久地存放在磁盤中,這個也算事務的一條特性,�0�2它就是:持久性(Durability)。數據庫
概括一下,以上一共提到了事務的 4 條特性,把它們的英文單詞首字母合起來就是:ACID,這個就是傳說中的「事務 ACID 特性」!安全
真的是很是牛逼的特性啊!這 4 條特性,是事務管理的基石,必定要透徹理解。此外還要明確,這四個傢伙當中,誰纔是老大?併發
其實想一想也就清楚了:原子性是基礎,隔離性是手段,持久性是目的,真正的老大就是一致性。數據不一致了,就至關於「江湖亂套了,流氓戴胸罩」。因此說,這三個小弟都是跟着「一致性」這個老大混,爲他全心全意服務。高併發
這四個傢伙當中,其實最難理解的反倒不是一致性,而是隔離性。由於它是保證一致性的重要手段,是工具,使用它不能有半點差池,不然後果自負!怪不得數據庫行業專家們都要來研究所謂的事務隔離級別了。其實,定義這四個級別就是爲了解決數據在高併發下所產生的問題,那又有哪些問題呢?工具
- Dirty Read(髒讀)
- Unrepeatable Read(不可重複讀)
- Phantom Read(幻讀)
首先看看「髒讀」,看到「髒」這個字,我就想到了噁心、骯髒。數據怎麼可能髒呢?其實也就是咱們常常說的「垃圾數據」了。好比說,有兩個事務,它們在併發執行(也就是競爭)。看看如下這個表格,您必定會明白我在說什麼:
時間 | 事務 A(存款) | 事務 B(取款) |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢餘額(1000 元) | |
T4 | 取出 1000 元(餘額 0 元) | |
T5 | 查詢餘額(0 元) | |
T6 | 撤銷事務(餘額恢復爲 1000 元) | |
T7 | 存入 500 元(餘額 500 元) | |
T8 | 提交事務 |
餘額應該爲 1500 元纔對!請看 T5 時間點,事務 A 此時查詢餘額爲 0 元,這個數據就是髒數據,它是事務 B 形成的,明顯事務沒有進行隔離,滲過來了,亂套了。
因此髒讀這件事情是很是要不得的,必定要解決掉!讓事務之間隔離起來纔是硬道理。
那第 2 條,不可重複讀又怎麼解釋呢?仍是用相似的例子來講明:
時間 | 事務 A(存款) | 事務 B(取款) |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢餘額(1000 元) | |
T4 | 查詢餘額(1000 元) | |
T5 | 取出 1000 元(餘額 0 元) | |
T6 | 提交事務 | |
T7 | 查詢餘額(0 元) |
事務 A 其實除了查詢了兩次之外,其餘什麼事情都沒有作,結果錢就從 1000 變成 0 了,這就是重複讀了。可想而知,這是別人乾的,不是我乾的。其實這樣也是合理的,畢竟事務 B 提交了事務,數據庫將結果進行了持久化,因此事務 A 再次讀取天然就發生了變化。
這種現象基本上是能夠理解的,但在有些變態的場景下倒是不容許的。畢竟這種現象也是事務之間沒有隔離所形成的,但咱們對於這種問題,彷佛能夠忽略。
最後一條,幻讀。我去!Phantom 這個單詞不就是「幽靈、鬼魂」嗎?剛看到這個單詞時,真的把個人小弟弟都給驚呆了。怪不得這裏要翻譯成「幻讀」了,總不能翻譯成「幽靈讀」、「鬼魂讀」吧。其實意義就是鬼在讀,不是人在讀,或者說搞不清楚爲何,它就變了,很暈,真的很暈。仍是用一個示例來講話吧:
時間 | 事務 A(統計總存款) | 事務 B(存款) |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 統計總存款(10000 元) | |
T4 | 存入 100 元 | |
T5 | 提交事務 | |
T6 | 統計總存款(10100 元) |
銀行工做人員,每次統計總存款,都看到不同的結果。不過這也確實也挺正常的,總存款增多了,確定是這個時候有人在存錢。可是若是銀行系統真的這樣設計,那算是玩完了。這一樣也是事務沒有隔離所形成的,但對於大多數應用系統而言,這彷佛也是正常的,能夠理解,也是容許的。銀行裏那些噁心的那些系統,要求很是嚴密,統計的時候,甚至會將全部的其餘操做給隔離開,這種隔離級別就算很是高了(估計要到 SERIALIZABLE 級別了)。
概括一下,以上提到了事務併發所引發的跟讀取數據有關的問題,各用一句話來描述一下:
- 髒讀:事務 A 讀取了事務 B 未提交的數據,並在這個基礎上又作了其餘操做。
- 不可重複讀:事務 A 讀取了事務 B�0�2已提交的更改數據。
- 幻讀:事務 A 讀取了事務 B 已提交的新增數據。
第一條是堅定抵制的,後兩條在大多數狀況下可不做考慮。
這就是爲何必需要有事務隔離級別這個東西了,它就像一面牆同樣,隔離不一樣的事務。看下面這個表格,您就清楚了不一樣的事務隔離級別能處理怎樣的事務併發問題:
事務隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
READ_UNCOMMITTED | 容許 | 容許 | 容許 |
READ_COMMITTED | 禁止 | 容許 | 容許 |
REPEATABLE_READ | 禁止 | 禁止 | 容許 |
SERIALIZABLE | 禁止 | 禁止 | 禁止 |
根據您的實際需求,再參考這張表,最後肯定事務隔離級別,應該再也不是一件難事了。
JDBC 也提供了這四類事務隔離級別,但默認事務隔離級別對不一樣數據庫產品而言,倒是不同的。咱們熟知的 MySQL 數據庫的默認事務隔離級別就是�0�2READ_COMMITTED,Oracle、SQL Server、DB2等都有有本身的默認值。我認爲 READ_COMMITTED 已經能夠解決絕大多數問題了,其餘的就具體狀況具體分析吧。
若對其餘數據庫的默認事務隔離級別不太清楚,可使用如下代碼來獲取:
1
2
|
DatabaseMetaData
meta = DBUtil.getDataSource().getConnection().getMetaData();
int defaultIsolation = meta.getDefaultTransactionIsolation();
|
提示:在 java.sql.Connection 類中可查看全部的隔離級別。
咱們知道 JDBC 只是鏈接 Java 程序與數據庫的橋樑而已,那麼數據庫又是怎樣隔離事務的呢?其實它就是「鎖」這個東西。當插入數據時,就鎖定表,這叫「鎖表」;當更新數據時,就鎖定行,這叫「鎖行」。固然這個已經超出了咱們今天討論的範圍,因此仍是留點空間給咱們的 DBA 同窗吧,省得他沒啥好寫的了。
除了 JDBC 給咱們提供的事務隔離級別這種解決方案之外,還有哪些解決方案能夠完善事務管理功能呢?
不妨看看 Spring 的解決方案吧,其實它是對 JDBC 的一個補充或擴展。它提供了一個很是重要的功能,就是:事務傳播行爲(Transaction Propagation Behavior)。
確實夠牛逼的,Spring 一會兒就提供了 7 種事務傳播行爲,這 7 種行爲一出現,真的是亮瞎了個人狗眼!
- PROPAGATION_REQUIRED
- RROPAGATION_REQUIRES_NEW
- PROPAGATION_NESTED
- PROPAGATION_SUPPORTS
- PROPAGATION_NOT_SUPPORTED
- PROPAGATION_NEVER
- PROPAGATION_MANDATORY
看了�0�2Spring 參考手冊以後,更是暈了,這究竟是在幹嗎?
首先要明確的是,事務是從哪裏來?傳播到哪裏去?答案是,從方法 A 傳播到方法 B。Spring 解決的只是方法之間的事務傳播,那狀況就多了,好比:
- 方法 A 有事務,方法 B 也有事務。
- 方法 A 有事務,方法 B 沒有事務。
- 方法 A 沒有事務,方法 B 有事務。
- 方法 A 沒有事務,方法 B 也沒有事務。
這樣就是 4 種了,還有 3 種特殊狀況。仍是用個人 Style 給你們作一個分析吧:
假設事務從方法 A 傳播到方法 B,您須要面對方法 B,問本身一個問題:
方法 A 有事務嗎?
- 若是沒有,就新建一個事務;若是有,就加入當前事務。這就是�0�2PROPAGATION_REQUIRED,它也是 Spring 提供的默認事務傳播行爲,適合絕大多數狀況。
- 若是沒有,就新建一個事務;若是有,就將當前事務掛起。這就是�0�2RROPAGATION_REQUIRES_NEW,意思就是建立了一個新事務,它和原來的事務沒有任何關係了。
- 若是沒有,就新建一個事務;若是有,就在當前事務中嵌套其餘事務。這就是�0�2PROPAGATION_NESTED,也就是傳說中的「嵌套事務」了,所嵌套的子事務與主事務之間是有關聯的(當主事務提交或回滾,子事務也會提交或回滾)。
- 若是沒有,就以非事務方式執行;若是有,就使用當前事務。這就是�0�2PROPAGATION_SUPPORTS,這種方式很是隨意,沒有就沒有,有就有,有點無所謂的態度,反正我是支持你的。
- 若是沒有,就以非事務方式執行;若是有,就將當前事務掛起。這就是�0�2PROPAGATION_NOT_SUPPORTED,這種方式很是強硬,沒有就沒有,有我也不支持你,把你掛起來,不鳥你。
- 若是沒有,就以非事務方式執行;若是有,就拋出異常。這就是�0�2PROPAGATION_NEVER,這種方式更猛,沒有就沒有,有了反而報錯,確實夠牛的,它說:我從不支持事務!
- 若是沒有,就拋出異常;若是有,就使用當前事務。這就是�0�2PROPAGATION_MANDATORY,這種方式能夠說是牛逼中的牛逼了,沒有事務直接就報錯,確實夠狠的,它說:我必需要有事務!
看到我上面這段解釋,小夥伴們是否已經感覺到,被打通任督二脈的感受?多讀幾遍,體會一下,就是您本身的東西了。
須要注意的是�0�2PROPAGATION_NESTED,不要被它的名字所欺騙,Nested(嵌套),因此凡是在相似方法 A 調用方法 B 的時候,在方法 B 上使用了這種事務傳播行爲,若是您真的那樣作了,那您就錯了。由於您錯誤地覺得�0�2PROPAGATION_NESTED 就是爲方法嵌套調用而準備的,其實默認的�0�2PROPAGATION_REQUIRED 就能夠幫助您,作您想要作的事情了。
Spring 給咱們帶來了事務傳播行爲,這確實是一個很是強大而又實用的功能。除此之外,也提供了一些小的附加功能,好比:
- 事務超時(Transaction Timeout):爲了解決事務時間太長,消耗太多的資源,因此故意給事務設置一個最大時常,若是超過了,就回滾事務。
- 只讀事務(Readonly Transaction):爲了忽略那些不須要事務的方法,好比讀取數據,這樣能夠有效地提升一些性能。
最後,推薦你們使用 Spring 的註解式事務配置,而放棄 XML 式事務配置。由於註解實在是太優雅了,固然這一切都取決於您自身的狀況了。
在 Spring 配置文件中使用:
1
2
3
|
...
<tx:annotation-driven />
...
|
在須要事務的方法上使用:
1
2
3
4
|
Transactional
public
void xxx() {
...
}
|
可在�0�2@Transactional 註解中設置:事務隔離級別、事務傳播行爲、事務超時時間、是否只讀事務。