說說數據庫事務

多條 SQL 語句,要麼所有執行成功,要麼所有執行失敗。sql

1 特性

數據庫事務必須同時知足 4 個特性 ( ACID )。數據庫

特性 說明
原子性 Atomic 表示組成一個事務的屢次數據庫操做是一個不可分割的原子單元,只有全部的操做都執行成功,才提交整個事務 。 事務中的任何一次數據庫操做失敗,已經執行操做都必須回滾,讓數據庫返回到操做前的狀態 。
一致性 Consistency 事務操做後,數據庫所處的狀態和它的業務規則是一致的 。好比 A 帳戶轉帳到 B 帳戶,無論操做是否異常, A 帳戶與 B 帳戶的總額是不變的。
隔離性 Isolation 在併發操做數據時,不一樣的事務擁有各自的數據空間,它們的操做既可能地不對對方產生干擾。數據庫規定了多種事務隔離級別,不一樣的隔離級別對應不一樣的干擾程度 。 隔離級別越高,數據一致性越好,但併發性越差。
持久性 Durability 一旦事務提交成功,事務中全部的數據都必須被持久化到數據庫中 。 即便在提交事務後數據庫發生崩潰,那麼當數據庫重啓時,也必須保證可以根據日誌恢復數據 。

在這些事務特性中,數據的 「 一致性 」 是最終目標, 其餘特性都是爲了達到這個目標而採起的措施或要求。bash

數據庫管理系統採用數據庫鎖來保證事物的隔離性,當多個事務試圖對相同的數據執行操做時,只有持有鎖的事務才能真正操做數據。併發

Oracle 採用了數據版本機制,在回滾階段爲數據的每一種變化都保留了一個版本,修改數據不會影響讀取數據 。oracle

2 併發問題

數據庫中的相同數據,可能同時被多個事務所訪問。因此,若是沒有采起必要的隔離措施,就會致使各類併發問題,從而破壞數據的完整性 。性能

併發問題能夠歸結爲 5 類,包括 3 類數據讀問題(髒讀 、 不可重複度 、 幻讀)和 2 類數據更新問題(第一類丟失更新和第二類丟失更新)。url

2.1 髒讀(dirty read)

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 元纔是)。日誌

2.2 不可重複讀(unrepeatable read)

不可重複讀指的是事務在不一樣的時間點,讀取到的數據不一樣。code

時間序列 事務 A 事務 B
1 開始事務 開始事務
2 - 查詢帳戶餘額(100 元)
3 查詢帳戶餘額(100 元) -
4 - 取款 10 元
5 - 提交事務(帳戶餘額:90 元)
6 查詢帳戶餘額(90 元) -

在時間序列 6,與在時間序列 3 時查詢到的餘額不一樣,發生不可重複讀現象。

2.3 幻讀(phantom read)

幻象讀通常發生在計算統計數據的事務中 。 A 事務讀取了 B 事務提交的新增數據,這時 A 事務將出現幻象讀的問題 。

假設在同一個事務中,兩次統計名某銀行支行全部帳戶的總金額,在兩次統計過程當中,恰好新增了一個存款帳戶 。那麼,這兩次統計的總金額確定會不一致 。

時間序列 事務 A 事務 B
1 開始事務 開始事務
2 統計(總金額:10 w) -
3 - 新增存款帳戶(金額:1 w)
4 - 提交事務(總金額:11 w)
5 統計(總金額:11 w)幻讀 -

2.4 不可重複讀與幻讀比較

比較 不可重複讀 幻讀
讀取對象 讀到其它事務已經提交的修改或刪除數據。 讀到其它事務已經提交的新增數據。
採起措施 對所要操做的數據添加級鎖,避免這些數據發生變化。 對所要操做的數據所在表添加級鎖,即將整張表鎖定(在 Oracle 中,是以多版本數據的方式實現的)。

2.5 第一類丟失更新

A 事務回滾時,把 B 事務中已經提交的更新數據給覆蓋咯 。

時間序列 事務 A 事務 B
1 開始事務 開始事務
2 查詢帳戶餘額(100 元) -
3 - 查詢帳戶餘額(100 元)
4 - 取款 10 元
5 - 提交事務(帳戶餘額:90 元)
6 存入 10 元 -
7 提交事務(帳戶餘額:110 元) -

這個問題影響很大。這個例子中,帳戶餘額應該仍是 100 元(取款 10 元,存入 10 元,實際對帳戶無影響),但由於存在第一類丟失更新,致使銀行損失 10 元。若是事務 A 先提交,那麼帳戶將損失 10 元。

2.6 第二類丟失更新

A 事務提交後覆蓋了 B 事務已經提交的數據,致使 B 事務所作操做丟失。

時間序列 事務 A 事務 B
1 開始事務 開始事務
2 - 查詢帳戶餘額:100 元
3 查詢帳戶餘額:100 元 -
4 - 取款 10 元
5 - 提交事務(帳戶餘額:90 元)
6 存款 10 元 -
7 提交事務(帳戶餘額:110 元) -

上述示例,直接致使銀行損失 10 元。若是 A 事務先提交,那麼將致使帳戶損失 10 元。

3 鎖機制

分類方式 類別
鎖定對象 表鎖定(整張表)、行鎖定(特定行)
併發事務鎖定關係 共享鎖定(運行其它的共享鎖定,但防止獨佔鎖定)、獨佔鎖定(防止任何鎖定)

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 顯式獲取。 全部鎖定 -

上式表中的防止與容許列都是針對其它會話而言的。

4 事務的隔離級別

由於直接使用鎖比較麻煩,因此數據庫爲咱們設置了事務的隔離級別,這些級別實現了自動鎖機制 。 設置好事務的隔離級別後,數據庫就會分析事務中的 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。

5 JDBC 事務

咱們能夠經過 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 語句由於在保存點以後,因此被回滾咯。

相關文章
相關標籤/搜索