在NoSql和內存數據庫如此流行的今天,在談關系型數據庫的貌似有點落伍了,不過在傳統軟件行業和對數據一致性和安全性要求比較高的行業,關系型數據庫還是比較普遍的。正好最近看到一個數據庫事務相關的知識,自己在這幾年的工作中用的比較多,也在事務上面犯過很多的錯誤,正好借這個機會整理以下。
事務的ACID屬性
A(Atomicity)原子性: 在一個事務上下文里面,對數據庫進行的任何操作,必須保證是原子的,也就是說要么不做,要么全部都做,不能只做一部分。比如insert一條數據和delete一條數據,不知能只做insert操作而不做delete操作
C(Consistency)一致性:在事務的處理過程中,數據庫必須時刻要避免被置於不一致 (inconsistent)的狀態。這意味着在事務期間,每次對數據庫實施的插入、更新或刪除操作 時,數據庫的完整性約束(integrity constraints)都要得到保證,即使在事務還未被提交時 也必須如此。比如非空約束。
I(Isolation)隔離性:兩個不同的事務相互之間是彼此隔離程度。有一種說法是事務之間是彼此隔離的,一個事務不能夠讀取另一個事務未提交的數據,這個不太准確,這個屬於事務的隔離級別。wiki上的解釋是“
The isolation property ensures that the concurrent execution of transactions results in a system state that could have been obtained if transactions are executed serially, i.e. one after the other“
D(Durability)持久性:事務一旦提交,在事務中對數據庫進行的修改也就進行持久化存儲了,不會由於系統故障導致提交后的數據丟失。
ACID是關系型數據庫最重要的特性。
事務的隔離級別
這個和ACID中的隔離性有關,主要分為四個級別
1. 讀未提交(Read UnCommited)
這個是最弱的隔離級別,不滿足ACID中的隔離性的要求,大多數數據庫並不提供這個支持。見下面的例子:
由於事務B讀取了事務未提交的數據,一旦回滾,事務B讀取的數據就是有問題的。
2. 讀已提交(Read Commited)
這個級別不允許事務B讀取事務A還未提交的 update操作更新后的數據,但是對於事務A的insert操作,在未提交之前,對事務B還是可見的。這就可能出現幻讀。見下面的例子
這里面不會出現臟讀,保證事務B讀取的都是事務A update提交之后的數據。但是對於insert操作,就可能存在臟讀的問題,例如下面一個例子
數據庫里面存在ABC這一條記錄,事務A新增加了XYZ這一條記錄,如果事務A正常提交,事務B讀取的結果沒有問題,如果事務A回滾,那么事務B就出現的臟讀,多了XYZ這一條記錄。
記得曾經自己在這個上面弄了一個bug,大概的業務邏輯是:先根據批日期,查詢某一天的總數據量,然后在查詢明細數據,生成到文件里面,然后更新狀態,這些都在一個事務里面。事務的隔離級別是默認的。當初發現查詢的總數據量和生成的明細數據數據不一致,原來在事務里面,還會有其他的事務往表里面插入數據,無論是否最終提交,都會被查詢出來,生成到文件里面。
3. 可重復讀(Repeatable Read)
這個隔離級別對於insert的數據提交之后才對另外一個事務可見,不會存在幻讀的情況。
但是事務之間還是會存在互相影響的情況,見下面的例子
事務A和事務B都是先讀取Price的價格,然后在價格上面減去一定的數值,我們期望結果是70,但是實際結果可能是90,也可能是80。
4. 可序列化
事務只能順序的讀取數據,當一個事務在讀取和修改數據的時候,另外一個事務只能掛起,直到正在讀取和修改數據的事務提交之后,掛起的事務才能執行。
事務的隔離級別與並發性,一致性之間存在關系,隔離級別越高,並發性就越低,一致性就越高
這幾種隔離級別並不是每個數據庫都會提供,oracle沒有提供第一種隔離級別“讀未提交數據”,一般情況下,大多數數據庫的默認隔離級別就是“讀提交數據”。
這些隔離級別都是定義在java.sql. Connection中,在獲取連接的時候我們可以進行設置,但是一般情況下,系統會用數據庫默認的級別來設置。
Connection.TRANSACTION_READ_UNCOMMITTED;
Connection.TRANSACTION_READ_COMMITTED;
Connection.TRANSACTION_REPEATABLE_READ;
Connection.TRANSACTION_SERIALIZABLE;
聲明式編程中事務的屬性
我們在使用spring活着ejb聲明式編程的時候,還會接觸到事務的傳播屬性,在spring的 TransactionDefinition 類里面進行定義。具體有以下幾個數值
Required(需要)
Mandatory(強制必須)
RequiresNew(需要新的)
Supports(支持)
NotSupported(不支持)
Never(不用)
Required:當前方法必須要求開啟事務,如果當前線程不存在事務,則開啟新的事務,如果當前線程已經存在事務,就加入到當前事務。這個是經常使用的。但是要注意的就是一旦事務中某一個方法回滾,當前事務上下文里面所有的操作都回滾,考慮到下面一個例子:
methodA{ read A =100 ; If(methodB()){ A = A + 1; }else{ A = A - 1; } Update A; Commit; }
|
methodB{ read B ; B = B – 1 Update B;
If(B>0){ Commit; Return ture; }else{ Rollback Return false; } }
|
假設A和B的事務都是Required,那么當調用MethodA的時候,如果method回滾了,對A的修改也就回滾了。所以上面的代碼不會達到預期的結果,也就是說A不可能修改成為99。
Required New:當前方法必須要求開啟新的事務,如果當前線程已經存在事務上下文,就暫停當前事務,等到新事務結束之后,在繼續恢復之前的事務。就拿上面的例子來說,methodB的對事務的修改不會影響到methodA。兩個事務之間不會互相影響。經常可以用到的場景就是在業務發生異常的時候發送短消息。如果業務發生異常,業務回滾,但是由於發送段消息是新的事務,不會受到業務異常的影響。
Mondary:當前方法必須要求事務,如果當前線程不存在事務,就拋出異常,如果存在,就加入到事務里。
Support:當前方法支持事務,如果當前線程存在事務,就加入到事務中去,如果不存在,不做任何操作。
Not Support:當前方法不支持事務,如果當前線程存在事務,就掛起當前事務,執行完當前方法,恢復事務。一般情況下在查詢的時候使用,如果一個方法只是查詢,並且非常耗時,就可以使用Not Support,避免事務時間超長。
Never:當前方法不支持事務,如果當前線程存在事務,則拋出異常。這種用的比較少。