多條 SQL 語句,要麼所有執行成功,要麼所有執行失敗。sql
數據庫事務必須同時知足 4 個特性 ( ACID )。數據庫
特性 | 說明 |
---|---|
原子性 Atomic | 表示組成一個事務的屢次數據庫操做是一個不可分割的原子單元,只有全部的操做都執行成功,才提交整個事務 。 事務中的任何一次數據庫操做失敗,已經執行操做都必須回滾,讓數據庫返回到操做前的狀態 。 |
一致性 Consistency | 事務操做後,數據庫所處的狀態和它的業務規則是一致的 。好比 A 帳戶轉帳到 B 帳戶,無論操做是否異常, A 帳戶與 B 帳戶的總額是不變的。 |
隔離性 Isolation | 在併發操做數據時,不一樣的事務擁有各自的數據空間,它們的操做既可能地不對對方產生干擾。數據庫規定了多種事務隔離級別,不一樣的隔離級別對應不一樣的干擾程度 。 隔離級別越高,數據一致性越好,但併發性越差。 |
持久性 Durability | 一旦事務提交成功,事務中全部的數據都必須被持久化到數據庫中 。 即便在提交事務後數據庫發生崩潰,那麼當數據庫重啓時,也必須保證可以根據日誌恢復數據 。 |
在這些事務特性中,數據的 「 一致性 」 是最終目標, 其餘特性都是爲了達到這個目標而採起的措施或要求。bash
數據庫管理系統採用數據庫鎖來保證事物的隔離性,當多個事務試圖對相同的數據執行操做時,只有持有鎖的事務才能真正操做數據。併發
Oracle 採用了數據版本機制,在回滾階段爲數據的每一種變化都保留了一個版本,修改數據不會影響讀取數據 。oracle
數據庫中的相同數據,可能同時被多個事務所訪問。因此,若是沒有采起必要的隔離措施,就會致使各類併發問題,從而破壞數據的完整性 。性能
併發問題能夠歸結爲 5 類,包括 3 類數據讀問題(髒讀 、 不可重複度 、 幻讀)和 2 類數據更新問題(第一類丟失更新和第二類丟失更新)。url
A 事務讀取了 B 事務還沒有提交的更改數據,並在此數據的基礎上進行操做 。 若是此時 B 事務回滾,那麼 A 事務以前讀到的數據就是髒數據。spa
時間序列 | 事務 A | 事務 B |
---|---|---|
1 | 開始事務 | 開始事務 |
2 | - | 查詢帳戶餘額(100 元) |
3 | - | 取出 50 元 |
4 | 查詢帳戶餘額(50 元)【髒讀】 | - |
5 | - | 回滾事務(帳戶餘額:100 元) |
6 | 存入 100 元 | - |
7 | 提交事務(帳戶餘額:150 元) | - |
這裏由於發生髒讀,致使帳戶損失了 50 元(事務 A 存款 100 元,事務 B 無影響,再加上原來的帳戶餘額 100 元,最後的帳戶餘額應該是 200 元纔是)。日誌
不可重複讀指的是事務在不一樣的時間點,讀取到的數據不一樣。code
時間序列 | 事務 A | 事務 B |
---|---|---|
1 | 開始事務 | 開始事務 |
2 | - | 查詢帳戶餘額(100 元) |
3 | 查詢帳戶餘額(100 元) | - |
4 | - | 取款 10 元 |
5 | - | 提交事務(帳戶餘額:90 元) |
6 | 查詢帳戶餘額(90 元) | - |
在時間序列 6,與在時間序列 3 時查詢到的餘額不一樣,發生不可重複讀現象。
幻象讀通常發生在計算統計數據的事務中 。 A 事務讀取了 B 事務提交的新增數據,這時 A 事務將出現幻象讀的問題 。
假設在同一個事務中,兩次統計名某銀行支行全部帳戶的總金額,在兩次統計過程當中,恰好新增了一個存款帳戶 。那麼,這兩次統計的總金額確定會不一致 。
時間序列 | 事務 A | 事務 B |
---|---|---|
1 | 開始事務 | 開始事務 |
2 | 統計(總金額:10 w) | - |
3 | - | 新增存款帳戶(金額:1 w) |
4 | - | 提交事務(總金額:11 w) |
5 | 統計(總金額:11 w)幻讀 | - |
比較 | 不可重複讀 | 幻讀 |
---|---|---|
讀取對象 | 讀到其它事務已經提交的修改或刪除數據。 | 讀到其它事務已經提交的新增數據。 |
採起措施 | 對所要操做的數據添加行級鎖,避免這些數據發生變化。 | 對所要操做的數據所在表添加表級鎖,即將整張表鎖定(在 Oracle 中,是以多版本數據的方式實現的)。 |
A 事務回滾時,把 B 事務中已經提交的更新數據給覆蓋咯 。
時間序列 | 事務 A | 事務 B |
---|---|---|
1 | 開始事務 | 開始事務 |
2 | 查詢帳戶餘額(100 元) | - |
3 | - | 查詢帳戶餘額(100 元) |
4 | - | 取款 10 元 |
5 | - | 提交事務(帳戶餘額:90 元) |
6 | 存入 10 元 | - |
7 | 提交事務(帳戶餘額:110 元) | - |
這個問題影響很大。這個例子中,帳戶餘額應該仍是 100 元(取款 10 元,存入 10 元,實際對帳戶無影響),但由於存在第一類丟失更新,致使銀行損失 10 元。若是事務 A 先提交,那麼帳戶將損失 10 元。
A 事務提交後覆蓋了 B 事務已經提交的數據,致使 B 事務所作操做丟失。
時間序列 | 事務 A | 事務 B |
---|---|---|
1 | 開始事務 | 開始事務 |
2 | - | 查詢帳戶餘額:100 元 |
3 | 查詢帳戶餘額:100 元 | - |
4 | - | 取款 10 元 |
5 | - | 提交事務(帳戶餘額:90 元) |
6 | 存款 10 元 | - |
7 | 提交事務(帳戶餘額:110 元) | - |
上述示例,直接致使銀行損失 10 元。若是 A 事務先提交,那麼將致使帳戶損失 10 元。
分類方式 | 類別 |
---|---|
鎖定對象 | 表鎖定(整張表)、行鎖定(特定行) |
併發事務鎖定關係 | 共享鎖定(運行其它的共享鎖定,但防止獨佔鎖定)、獨佔鎖定(防止任何鎖定) |
oracle 數據庫中常見的鎖定:
鎖定 | 說明 | 防止 | 容許 |
---|---|---|---|
行共享鎖定 | 可經過 select for update 語句隱式得到該鎖定,或者經過 LOCK TABLE IN ROW SHARE MODE 語句顯式獲取 。 |
表獨佔鎖定 | 行共享鎖定、行獨佔鎖定、表共享行獨佔鎖定 |
行獨佔鎖定 | 可經過 insert、update、delete 語句隱式獲取,或者經過 LOCK TABLE IN ROW EXCLUSIVE MODE 語句顯式獲取 。 |
行或表共享鎖定、行或表獨佔鎖定 | - |
表共享鎖定 | 可經過 LOCK TABLE IN SHARE MODE 語句顯式獲取。該鎖定可讓會話具備對錶事務級的一致性訪問,由於其餘會話在用戶提交或者回滾該事務並釋放對該表的鎖定以前,不能更改這張表 。 |
表共享行獨佔鎖定、表獨佔鎖定 | 行共享鎖定、表共享鎖定 |
表共享行獨佔鎖定 | 可經過 LOCK TABLE IN SHARE ROW EXCLUSIVE MODE 語句顯式獲取。 |
表共享行獨佔鎖定、行獨佔鎖定、表獨佔鎖定 | 其它行的共享鎖定 |
表獨佔鎖定 | 可經過 LOCK TABLE IN EXCLUSIVE MODE 顯式獲取。 |
全部鎖定 | - |
上式表中的防止與容許列都是針對其它會話而言的。
由於直接使用鎖比較麻煩,因此數據庫爲咱們設置了事務的隔離級別,這些級別實現了自動鎖機制 。 設置好事務的隔離級別後,數據庫就會分析事務中的 SQL 語句,而後自動爲事務所操做的數據加上適合的鎖 。 並且,數據庫還會維護這些鎖,當一個資源上的鎖數目太多時,就會自動升級,從而提升系統的運行性能。這些過程對咱們來講是徹底透明的。
ANSI/ISO SQL 92 定義了 4 個等級的隔離級別:
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 | 第一類丟失更新 | 第二類丟失更新 |
---|---|---|---|---|---|
READ UNCOMMITTED | 容許 | 容許 | 容許 | 不容許 | 容許 |
READ COMMITTED | 不容許 | 容許 | 容許 | 不容許 | 容許 |
REPEATABLE_READ | 不容許 | 不容許 | 容許 | 不容許 | 不容許 |
SERIALIZABLE | 不容許 | 不容許 | 不容許 | 不容許 | 不容許 |
隔離級別與併發性是對立的,READ UNCOMMITTED 併發性最高,而 SERIALIZABLE 的併發性最低。
由於 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 connection = null;
try {
String url = "xxx";
//獲取數據庫鏈接
connection = DriverManager.getConnection(url);
//關閉自動提交機制
connection.setAutoCommit(false);
//設置事務隔離級別
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
Statement statement = connection.createStatement();
String sql = "xxx";
statement.execute(sql);
//提交事務
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾事務
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
複製代碼
JDBC2.0 中事務只有提交與回滾操做 。在 JDBC3.0 中(Java1.4+)引入了保存點( SavePoint 接口)。保存點能夠把事務分割爲多個階段,這樣咱們就能夠根據業務要求,來指定須要回滾到的特定保存點啦O(∩_∩)O~
咱們能夠經過 DatabaseMetaData 的 supportsSavepoints()
方法驗證所鏈接的數據庫是否支持保存點特性 。
Statement statement = connection.createStatement();
String sql1 = "xxx";
statement.execute(sql1);
//設置保存點
Savepoint savepoint=connection.setSavepoint();
String sql2 = "xxx";
statement.execute(sql2);
//回退到保存點
connection.rollback(savepoint);
複製代碼
若是事務提交了上段代碼, 那麼 sql1 語句將有效,而 sql2 語句由於在保存點以後,因此被回滾咯。