在數據庫中,事務是指一組邏輯工做單元執行的一系列動做,要麼都執行,要麼都不執行。 html
一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來的其餘操做和數據庫故障不該該對其有任何影響。 java
在這些事務特性中,數據「一致性」是最終目標,其餘的特性都是爲達到這個目標的措施、要求或手段。 sql
數據庫管理系統採用日誌來保證事務的原子性、一致性和持久性。日誌記錄了事務對數據庫所作的更新,若是某個事務在執行過程當中發生錯誤,就能夠根據日誌,撤銷事務對數據庫已作的更新,使數據庫退回到執行事務前的初始狀態。此外,對於已經提交的事務,即便數據庫崩潰,在重啓數據庫時也可以根據日誌對還沒有持久化的數據進行相應的重執行操做。 數據庫
數據庫管理系統採用鎖機制來實現事務的隔離性。當多個事務同時更新數據庫中相同的數據時,只容許持有鎖的事務能更新該數據,其餘事務必須等待,直到前一個事務釋放了鎖,其餘事務纔有機會更新該數據。Oracle數據庫還使用了數據版本的機制,在回滾段爲數據的每一個變化都保存一個版本,使數據的更改不影響數據的讀取。 併發
一個數據庫可能擁有多個訪問客戶端,這些客戶端均可以併發方式訪問數據庫。數據庫中的相同數據可能同時被多個事務訪問,若是沒有采起必要的隔離措施,就會致使各類併發問題,破壞數據的完整性。這些問題能夠歸結爲5類,包括3類數據讀問題(髒讀、不可重複讀和幻象讀)以及2類數據更新問題(第一類丟失更新和第二類丟失更新)。下面,咱們分別經過實例講解引起問題的場景。 性能
A事務讀取B事務還沒有提交的更改數據,並在這個數據的基礎上操做。若是恰巧B事務回滾,那麼A事務讀到的數據根本是不被認可的。來看取款事務和轉帳事務併發時引起的髒讀場景: spa
在這個場景中,B但願取款500元然後又撤銷了動做,而A往相同的帳戶中轉帳100元,就由於A事務讀取了B事務還沒有提交的數據,於是形成帳戶白白丟失了500元。在Oracle數據庫中,不會發生髒讀的狀況。 設計
在同一事務中,T4時間點和T7時間點讀取帳戶存款餘額不同。 日誌
若是新增數據恰好知足事務的查詢條件,這個新數據就進入了事務的視野,於是產生了兩個統計不一致的狀況。 code
幻象讀和不可重複讀是兩個容易混淆的概念,前者是指讀到了其餘已經提交事務的新增數據,然後者是指讀到了已經提交事務的更改數據(更改或刪除),爲了不這兩種狀況,採起的對策是不一樣的,防止讀取到更改數據,只須要對操做的數據添加行級鎖,阻止操做中的數據發生變化,而防止讀取到新增數據,則每每須要添加表級鎖——將整個表鎖定,防止新增數據(Oracle使用多版本數據的方式實現)。
A事務在撤銷時,「不當心」將B事務已經轉入帳戶的金額給抹去了。
A事務覆蓋B事務已經提交的數據,形成B事務所作操做丟失:
上面的例子裏因爲支票轉帳事務覆蓋了取款事務對存款餘額所作的更新,致使銀行最後損失了100元,相反若是轉帳事務先提交,那麼用戶帳戶將損失100元。
數據併發會引起不少問題,在一些場合下有些問題是容許的,但在另一些場合下可能倒是致命的。數據庫經過鎖的機制解決併發訪問的問題,雖然不一樣的數據庫在實現細節上存在差異,但原理基本上是同樣的。
按鎖定的對象的不一樣,通常能夠分爲表鎖定和行鎖定,前者對整個表進行鎖定,然後者對錶中特定行進行鎖定。從併發事務鎖定的關係上看,能夠分爲共享鎖定和獨佔鎖定。共享鎖定會防止獨佔鎖定,但容許其餘的共享鎖定。而獨佔鎖定既防止其餘的獨佔鎖定,也防止其餘的共享鎖定。爲了更改數據,數據庫必須在進行更改的行上施加行獨佔鎖定,INSERT、UPDATE、DELETE和SELECT FOR UPDATE語句都會隱式採用必要的行鎖定。下面咱們介紹一下Oracle數據庫經常使用的5種鎖定。
通常經過SELECT FOR UPDATE語句隱式得到行共享鎖定,在Oracle中用戶也能夠經過LOCK TABLE IN ROW SHARE MODE語句顯式得到行共享鎖定。行共享鎖定並不防止對數據行進行更改的操做,可是能夠防止其餘會話獲取獨佔性數據表鎖定。容許進行多個併發的行共享和行獨佔性鎖定,還容許進行數據表的共享或者採用共享行獨佔鎖定。
經過一條INSERT、UPDATE或DELETE語句隱式獲取,或者經過一條LOCK TABLE IN ROW EXCLUSIVE MODE語句顯式獲取。這個鎖定能夠防止其餘會話獲取一個共享鎖定、共享行獨佔鎖定或獨佔鎖定。
經過LOCK TABLE IN SHARE ROW EXCLUSIVE MODE語句顯式得到。這種鎖定能夠防止其餘會話獲取一個表共享、行獨佔或者表獨佔鎖定,它容許其餘行共享鎖定。這種鎖定相似於表共享鎖定,只是一次只能對一個表放置一個表共享行獨佔鎖定。若是A會話擁有該鎖定,則B會話能夠執行SELECT FOR UPDATE操做,但若是B會話試圖更新選擇的行,則須要等待。
經過LOCK TABLE IN EXCLUSIVE MODE顯式得到。這個鎖定防止其餘會話對該表的任何其餘鎖定。
所謂悲觀鎖就是基於數據庫機制實現的。好比在在使用select子句的時候加上for update,那麼直到改子句的事務結束爲止,任何應用都沒法修改select出來的記錄。
所謂樂觀鎖是基於應用的版本機制來實現的。通常會在表裏面設計一個版本字段version(我通常會把這個字段設爲長整形或者timestamp)。通常的update場景是這樣:
第一步:
select a, version from tb where id=1;假設獲得數據是:['xxx', 100]
第二步:
update tb set a='yyy', version=100+1 where version=100; //注意, version通常不會在業務操做的時候修改
這要求每一次update操做都變動版本字段,不然仍是要進程間的數據 仍是會被相互覆蓋。
樂觀鎖沒法鎖定其餘應用對數據的操做。樂觀鎖機制避免了長事務中的數據庫加鎖開銷(兩個事務,事務A 和事務B操做過程當中,都沒有對數據庫數據加鎖),大大提高了大併發量下的系統總體性能表現。 須要注意的是,樂觀鎖機制每每基於系統中的數據存儲邏輯,所以也具有必定的侷限性,因爲樂觀鎖機制通常是在咱們的系統中實現,來自外部系統的用戶餘額更新操做不受咱們系統的控制,所以可能會形成髒數據被更新到數據庫中。在系統設計階段,咱們應該充分考慮到這些狀況出現的可能性,並進行相應調整(如將樂觀鎖策略在數據庫存儲過程當中實現,對外只開放基於此存儲過程的數據更新途 徑,而不是將數據庫表直接對外公開)。
儘管數據庫爲用戶提供了鎖的DML操做方式,但直接使用鎖管理是很是麻煩的,所以數據庫爲用戶提供了自動鎖機制。只要用戶指定會話的事務隔離級別,數據庫就會分析事務中的SQL語句,而後自動爲事務操做的數據資源添加上適合的鎖。此外數據庫還會維護這些鎖,當一個資源上的鎖數目太多時,自動進行鎖升級以提升系統的運行性能,而這一過程對用戶來講徹底是透明的。
ANSI/ISO SQL 92標準定義了4個等級的事務隔離級別,在相同數據環境下,使用相同的輸入,執行相同的工做,根據不一樣的隔離級別,能夠致使不一樣的結果。不一樣事務隔離級別可以解決的數據併發問題的能力是不一樣的,以下表所示。
事務的隔離級別和數據庫併發性是對立的,二者此增彼長。通常來講,使用READ UNCOMMITED隔離級別的數據庫擁有最高的併發性和吞吐量,而使用SERIALIZABLE隔離級別的數據庫併發性最低。
SQL 92定義READ UNCOMMITED主要是爲了提供非阻塞讀的能力,Oracle雖然也支持READ UNCOMMITED,但它不支持髒讀,由於Oracle使用多版本機制完全解決了在非阻塞讀時讀到髒數據的問題並保證讀的一致性,因此,Oracle的READ COMMITTED隔離級別就已經知足了SQL 92標準的REPEATABLE READ隔離級別。
SQL 92推薦使用REPEATABLE READ以保證數據的讀一致性,不過用戶能夠根據應用的須要選擇適合的隔離等級。
並非全部的數據庫都支持事務,即便支持事務的數據庫也並不是支持全部的事務隔離級別,用戶能夠經過Connection#getMetaData()方法獲取DatabaseMetaData對象,並經過該對象的supportsTransactions()、supportsTransactionIsolationLevel(int level)方法查看底層數據庫的事務支持狀況。
Connection默認狀況下是自動提交的,也即每條執行的SQL都對應一個事務,爲了可以將多條SQL當成一個事務執行,必須先經過Connection#setAutoCommit(false)阻止Connection自動提交,並可經過Connection#setTransactionIsolation()設置事務的隔離級別,Connection中定義了對應SQL 92標準4個事務隔離級別的常量。經過Connection#commit()提交事務,經過Connection#rollback()回滾事務。下面是典型的JDBC事務數據操做的代碼:
Connection conn ; try{ conn = DriverManager.getConnection();//①獲取數據鏈接 conn.setAutoCommit(false); //②關閉自動提交的機制 conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); //③設置事務隔離級別 Statement stmt = conn.createStatement(); int rows = stmt.executeUpdate( "INSERT INTO t_topic VALUES(1,’tom’) " ); rows = stmt.executeUpdate( "UPDATE t_user set topic_nums = topic_nums +1 "+ "WHERE user_id = 1"); conn.commit();//④提交事務 }catch(Exception e){ … conn.rollback();//⑤回滾事務 }finally{ … }在JDBC 2.0中,事務最終只能有兩個操做:提交和回滾。可是,有些應用可能須要對事務進行更多的控制,而不是簡單地提交或回滾。JDBC 3.0(JDK 1.4及之後的版本)引入了一個全新的保存點特性,Savepoint 接口容許用戶將事務分割爲多個階段,用戶能夠指定回滾到事務的特定保存點,而並不是像JDBC 2.0同樣只回滾到開始事務的點,如圖所示。
下面的代碼使用了保存點的功能,在發生特定問題時,回滾到指定的保存點,而非回滾整個事務,代碼以下所示:
… Statement stmt = conn.createStatement(); int rows = stmt.executeUpdate( "INSERT INTO t_topic VALUES(1,’tom’)"); Savepoint svpt = conn.setSavepoint("savePoint1");//①設置一個保存點 rows = stmt.executeUpdate( "UPDATE t_user set topic_nums = topic_nums +1 "+ "WHERE user_id = 1"); … //②回滾到①處的savePoint1,①以前的SQL操做,在整個事務提交後依然提交, //但①到②之間的SQL操做被撤銷了 conn.rollback(svpt); … conn.commit();//③提交事務
並不是全部數據庫都支持保存點功能,用戶能夠經過DatabaseMetaData#supportsSavepoints()方法查看是否支持。
Spring事務機制詳解參見:http://www.open-open.com/lib/view/open1350865116821.html
文中大量內容摘自:http://stamen.iteye.com/blog/1541720