JDBC事務 (轉)

作者:Jack Shirazi

開發通過ACID測試的應用程序

事務使得開發人員的工作變得簡單多了。通過在JDBC API和諸如Oracle9i的關係數據庫中使用事務功能,在更新多用戶應用程序時,你可以把數據遭破壞的可能性降到最低。然而,事務需要處理開銷,與免 費事務應用程序(更容易被破壞)相比較,它會降低系統的性能。那麼,當使用事務時,什麼纔是保持性能的最好方法?

最佳的性能調優建議是避免做那些沒必要做的事情。事務處理是數據庫的大量工作,而且數據庫默認地維護多種資源以確保事務具有ACID(原子性,一致性,隔離性和持續性)屬性(查看"ACID Transaction Properties"工具欄獲取詳細信息)。這些數據庫資源管理多個數據併發操作以及提交和回滾操作,從而保證ACID事務屬性。如果你能減少數據庫的此類操作,就將提高應用程序的性能。讓我們看一些避免處理開銷並提高事務性能的方法。

自動提交模式

最大限度減少事務開銷的第一個方法是通過把多個操作移到一個單一事務中來合併 事務。默認情況下,JDBC連接工作在自動提交模式下,這就意味着每個發送到數據庫的操作都會作爲獨立事務自動執行。在這種情況下,每個 Statement.execute()方法調用都如同由一條BEGIN TRANSACTION命令開始,並由一條COMMIT命令結束。

關閉自動提交模式並且明確定義事務需要進行大量額外的工作,因爲你必須手動添加事務劃分語句(COMMIT和ROLLBACK)。但是合併事務可以減少性能開銷,特別是當你對你的系統進行伸縮時。(下面的"批量更新"部分會涉及到合併更新事務的技術細節。)在重負荷系統中,事務開銷意義重大。開銷越低,系統的可伸縮性就越好。

簡單地使用Connection.setAutoCommit(false)命令來關閉自動提交模式。

JDBC API還提供了一個Connection.getAutoCommit()方法來返回當前的自動提交模式。當你關閉了自動提交模式,你將需要使用兩個事務劃分方法:Connection.commit()和Connection.rollback()。

當人工控制事務時,需要遵循以下幾條原則:使事務儘可能保持簡短,不要在一個事務中包含很多操作使它們變得非常冗長。(使事務打開並保持行開鎖狀態,會影響其他事務並降低可伸縮性。)然而,如果幾項操作可以一項接一項地執行,那麼就把它們合併到一個事務中。

合併操作可能需要在你的SQL語句中增加額外的條件邏輯,並且可能需要臨時表。不管這個開銷,合併事務會更加有效,因爲數據庫可以在一個步驟內獲得所有需要的鎖,並在一個步驟內釋放它們。

當自動提交模式沒有關閉時,它所引起的更多事務會產生更多的通信開銷,更多的鎖定和釋放時間,以及與其他會話發生衝突的更大可能性。

批量更新

批量更新簡單地說就是在一個事務和一個數據庫調用中將多個DML語句(例如插 入、更新和刪除)發送到數據庫。JDBC通過Statement.addBatch()和Statement.executeBatch()方法支持這項 功能。批量更新的技巧相當簡單,在下文中會加以說明。記住關閉自動提交模式(確保一個批處理作爲一個事務執行),並且當你完成後這一切後,明確提交批事 務。

清單 1 中的示例使用了普通的JDBC Statement對象。另外,JDBC API提供了一個PreparedStatement類,它也可以用參數表示SQL語句。

此外,當你使用PreparedStatement 對象來代替Statement對象時,Oracle的JDBC批處理實施就可以得到優化。在Oracle JDBC中,Statement對象不會在一次網絡傳輸中傳送所有成批的SQL語句。由於這個限制,當成批傳送語句時可以使用 PreparedStatement對象,因爲PreparedStatement在一次批處理中會傳送所有的語句。

清單 2 給出了使用參數語句和PreparedStatement對象的相同批處理技巧。

藉助於所有批處理語句相同的查詢計劃,在PreparedStatement對象中利用參數語句使數據庫進一步優化批處理。如果沒有參數設定,語句就會各不相同,因而數據庫就不能重複使用查詢計劃。

雖然這種方法經常可以提高性能,但應注意以下幾點:處理開銷與創建查詢計劃的 聯合將導致第一次執行SQL語句時會比使用普通Statement對象時運行得更慢,而隨後準備好的執行語句將會快很多。(開發人員經常把首次 PreparedStatement批處理移動到應用程序中對時間要求低的部分加以執行。)使用PreparedStatement對象將比使用 Statement對象更有效,特別是當使用超過50條語句的大批量處理時。

以上的示例使用了JDBC規範所定義的標準批處理模式。 Oracle的JDBC實施提供了一種可選擇的批處理模式,它使用了一種被稱作 OraclePreparedStatement.setExecuteBatch(int)的新方法。在這種模式下,預設的語句被自動保存在客戶端,直 到語句的數量與setExecuteBatch(int)中的參數所定義的"批量值"相等。這樣一來,積累的語句在一次傳送中被髮送到數據庫。 Oracle所推薦的這種模式在某些情況下會比標準的批處理模式更快。當使用它的時候,調整批量值來優化你的應用程序中事務的性能。Oracle模式惟一 需要注意的一點是:它不是標準的--它使用官方JDBC規範所不支持的擴展功能。

事務隔離級別

事務被定義爲全有或全無操作。一個事務的ACID屬性確保每件事情都發生在一個事務,如同在事務期間在數據庫中沒有發生其他操作。由此可見,對數據庫來說,確保ACID屬性有很多工作要做。

JDBC Connection界面定義了五種事務隔離級別(在下面說明)。並不是所有的數據庫都支持所有的級別。例如,Oracle9i只支持TRANSACTION_READ_COMMITTED和TRANSACTION_ SERIALIZABLE這兩個級別。

許多數據庫,例如Oracle9i,提供了其他事務級別支持。這些級別不提供"真正的"事務,因爲它們不完全符合ACID屬性。然而,它們通過可接受的事務功能提供更好的性能,因此它們對很多操作類型是非常有用的。

在JDBC中定義的級別包括:

TRANSACTION_NONE。正式地 講,TRANSACTION_NONE不是一個有效的事務級別。根據java.sql Connection API文件,這個級別表示事務是不被支持的,因此理論上說你不能使用TRANSACTION_NONE作爲一個自變量賦給 Connection.setTransactionIsolation()方法。事實上,雖然一些數據庫實施了這個事務級別,但是Oracle9i卻沒 有實施。

TRANSACTION_READ_UNCOMMITTED。這是最快的完全 有效的事務級別。它允許你讀取其他還沒有被提交到數據庫的併發事務做出的修改。這個API文件指出,髒讀取(dirty reads)、不可重複讀取(non-repeatable reads)和錯誤讀取(phantom reads)都可以在這個事務級別發生(參閱" 一些非ACID事務問題 "部分)。這個級別意在支持ACID的"原子性(Atomic)"部分,在這個級別中,你的修改如果被提交,將被認爲是同時發生的;如果被撤銷,就被當作什麼也沒發生。Oracle9i不支持這個級別。

TRANSACTION_READ_UNCOMMITTED。這是最快的完全 有效的事務級別。它允許你讀取其他還沒有被提交到數據庫的併發事務做出的修改。這個API文件指出,髒讀取(dirty reads)、不可重複讀取(non-repeatable reads)和錯誤讀取(phantom reads)都可以在這個事務級別發生(參閱" 一些非ACID事務問題 "部分)。 這個級別意在支持ACID的"原子性(Atomic)"部分,在這個級別中,你的修改如果被提交,將被認爲是同時發生的;如果被撤銷,就被當作什麼也沒發生。Oracle9i不支持這個級別。

TRANSACTION_READ_COMMITTED。這是繼 TRANSACTION_READ_UNCOMMITTED之後最快的完全有效的級別。在此級別中,你可以讀取已經被提交到數據庫中的其他併發事務所做出 的修改。API文件指出,髒讀取在這個級別中是被禁止的,但是不可重複讀取和錯誤讀取都可以發生。這個級別是Oracle9i默認的級別。

TRANSACTION_REPEATABLE_READ。 這個級別比TRANSACTION_SERIALIZABLE快,但是比其他的事務級別要慢。讀取操作可以重複進行,這意味着兩次讀取同樣的域應該總是得 到同樣的值,除非事務本身改變了這個值。API文件指出,髒讀取和不可重複讀取在這個事務級別中是被禁止的,但是錯誤讀取可以發生。

從技術上講,數據庫通過在被讀取或寫入的行上加鎖來實施這個級別,並且保持鎖定狀態直到事務結束。這就防止了這些行被修改或刪除,但是不能防止額外的行被添加--因此,就可能產生錯誤讀取。Oracle9i不支持這個級別。

TRANSACTION_SERIALIZABLE。 這是最慢的事務級別,但是它完全與ACID兼容。"單詞可串行化(serializable)"指的就是ACID兼容,其中你的事務被認爲在整體上已經發 生,就如同其他所有已提交的事務在這個事務之前或之後全部發生。換句話說,事務被串行執行。

原子性指的是事務中的整個活動序列必須被全部完成或全部放棄。事務不能部分地完成。與隔離性相結合後(見表1),原子性指的是任意一個事務將查看任何其他同時或以原子形式發生的事務所採取的所有活動。

一致性指的是事務既可以建立一個新的、有效的數據狀態(在這種狀態中可以進行所有的改動),它可以在操作失敗的情況下,把所有的數據返回到事務發生之前已有的狀態。

隔離性指在一個事務中發生的所有活動對其他事務來說都是不可見的,直到該事務被提交。

持續性指的是事務成功做出並提交的所有改動是不變的,並且必須克服系統故障。比如說,如果發生了故障或者系統重新啓動,數據在最後提交事務之後所存在的狀態下是可用的。

ACID事務屬性 ACID指的時數據庫事務的基本屬性:原子性,一致性,隔離性和持續性。所有的Oracle事務全部符合這些屬性,雖然你可以在數據庫系統中進行手動設置級別來加強各個屬性(參閱"事務隔離級別"部分)。

髒讀取、不可重複讀取和錯誤讀取在TRANSACTION_SERIALIZABLE級別是全部被禁止的。從技術上講,數據庫通過鎖定在事務中使用的表來實施這個級別。Oracle9i支持這個級別(正如每個與符合ACID的數據庫那樣)。

開發通過ACID測試的應用程序

選擇正確的級別

你可以通過使用 Connection.setTransactionIsolation()方法設定一個連接的事務級別。類似地,你可以通過使用 Connection.getTransactionIsolation()方法獲得連接的當前事務級別。你可以通過使用 DatabaseMetaData.supportsTransaction IsolationLevel()方法確定由你的數據庫驅動程序所支持的事務級別,如 清單3 所示。

事務級別越高,數量越多、限制性更強的鎖就會被運用到數據庫記錄或者表中。同時,更多的鎖被運用到數據庫和它們的覆蓋面越寬,任意兩個事務衝突的可能性就越大。

如果有一個衝突(例如兩個事務試圖獲取同一個鎖),第一個事務必將會成功,然而第二個事務將被阻止直到第一個事務釋放該鎖(或者是嘗試獲取該鎖的行爲超時導致操作失敗)。

更多的衝突發生時,事務的執行速度將會變慢,因爲它們將花費更多的時間用於解決衝突(等待鎖被釋放)。

最大限度地增加應用程序的可伸縮性需要平衡地理解事務執行方法。一方面,你可 以通過將在事務中所執行的操作數量減到最少來優化應用程序,從而減少了單個事務所花費的時間。但是這樣就增加了事務的總數量,這可能增加衝突的風險。使用 批量操作,你可以最大限度地減少所執行事務的數量。

然而,這增加了單個事務的長度,也可能增加衝突的風險。在任意一種情況下,當你降低事務隔離級別時,事務使用的鎖就越少,因此越不會引起性能的下降。這樣做的風險是因爲沒有使用完全符合ACID的事務,從而損失了功能性。

如果你需要把事務執行時間減到最少的話,在你的整個應用程序中使用一個事務級別好像並不是很理想。在應用程序中查找讀取查詢,對於每個查詢,考慮在下面" 一些非ACID事務問題 " 部分列出的任何問題是否會對給定數據的查詢或數據更新模式產生負面影響。讀取靜態表,或者只被讀取它們的同一事務所更新的表,可以安全地使用最低的事務級 別。在那些不可能進行併發更新的事務中,你可以安全、高效地使用諸如TRANSACTION_ READ_COMMITTED這樣的級別。

一些非ACID事務問題

當一個連接使用了不完全符合ACID的 TRANSATION_SERIALIZABLE事務級別時,就會發生很多問題。下面的例子使用了一個名爲table_sizes的表,它有兩個字 段,tablename和tablesize。這個例子還使用了兩個事務,T1和T2,其中T1使用TRANSACTION_SERIALIZABLE級 別。

髒讀取。當一個事務能發現一行中有未提交的更改時,就發生了一次髒讀取。如果 另一個事務改變了一個值,你的事務可以讀取那個改變的值,但其他的事務將回滾其事務,使這個值無效或成爲髒值。例如,這裏給出了當T2使用事務級別 TRANSACTION_ READ_UNCOMMITTED時發生的情況,其中記錄爲tablename=users,tablesize=11。

表1:每個事務問題在每個允許的事務隔離級別中發生的可能性。
隔離級別 髒讀取 不可重複讀取 錯誤插入
讀取未提交數據
讀取已提交數據
可重複讀取
可串行化

1. T1和T2啓動它們的事務。
2. T1將記錄更新爲tablename=users,tablesize=12。
3. T2讀取T1未提交的修改,讀取記錄tablename=user,tablesize=12,因爲T2的事務級別意味着未提交的修改有時可以被讀取。
4. T1回滾事務,因此tablename=users行中tablesize=11。
5. T2仍有無效的記錄值:記錄tablename=users,tablesize=12。但是它可以通過髒表尺寸值工作,並且能在髒值的基礎上成功地提交修改。

不可重複讀取。當事務內一條記錄在事務被兩次讀取而沒有更新時,就發生了不可 重複讀取,而且,從兩次讀取中可以看到不同的結果。如果你的事務讀取一個值,並且另一事務提交了對這個值的一次修改(或刪除了這條記錄),然後你的事務便 可以讀取這個修改後的值(或發現這條記錄丟失),儘管你的事務還沒有提交或回滾。例如,這裏給出了當T2使用事務級別 TRANSACTION_READ_COMMITTED時發生的情況,其中記錄爲tablename=users,tablesize=11。

1. T1和T2啓動它們的事務。
2. T2讀取記錄tablename=users,tablesize=11。
3. T1將記錄更新爲tablename=users,tablesize=12並提交修改。
4. T2重新讀取記錄tablename=users,並且現在看到tablesize=12,因爲T2的事務級別意味着其他事務提交的修改可以被看到,儘管T2還沒有提交或回滾。

錯誤讀取。當一個事務讀取由另一個未提交的事務插入的一行時,就發生了錯誤讀 取。如果另一事務將一行插入到一個表裏,當你的事務查詢那個表時就能夠讀取那條新記錄,即使其他的事務相繼回滾。例如,這裏給出了當T2使用事務級別 TRANSACTION_REPEATABLE_READ時發生的情況,其中記錄爲tablename=users,tablesize=11。

1. T1和T2啓動它們的事務。
2. T2執行SELECT * FROM table_sizes WHERE tablesize>10並讀取一行,tablesize=11的行tablename=user。
3. T1插入tablename=groups,tablesize=28的記錄。
4. T2再次執行SELECT * FROM table_sizes WHERE tablesize>10並讀取兩條記錄: tablename=users,tablesize=11和tablename=groups,tablesize=28。
5. T1回滾事務,因此記錄tablename=goups,tablesize=28不再存在於table_sizes表中。

根據T2被讀取的數據集中所具有的一條額外錯誤記錄,T2可以成功地提交數據。

表1 中,你會發現在每一個所允許的事件隔離級別中發生事務問題的可能性列表。

用戶控制的事務

在許多應用程序中,用戶在一個事務結束以前,必須執行一個明確的動作(比如點 擊"確定"或"取消")。這些情況會導致很多問題。例如,如果用戶忘記了終止活動或者讓活動保持未完成的狀態,資源就一直在應用程序和數據庫中保持開放狀 態,這可能會在併發的活動和鎖定的資源之間產生衝突,降低了系統的性能。只有單一的用戶應用程序或用戶不共享資源的應用程序,纔不受這個問題的影響。

讓用戶處於一個JDBC事務控制下的主要解決方法是使用優化的事務。這些事務爲外部的JDBC事務更新收集信息,然後使用一種機制來檢查更新沒有與其他任一個可能在兩者間已經被處理的更新發生衝突。

檢查優化衝突的機制包括使用時間標記或更改計數器,從期望狀態檢查區別。例 如,當應用程序從用戶輸入收集用於更新的數據時,數據可以作爲一批包含時間標記的安全機制的SQL語句,被髮送到數據庫,以確保數據庫中的原始數據與最初 用於客戶應用程序的數據相同。一個成功的事務更新記錄,包括時間標記,顯示了最近修改的數據。如果來自另一用戶的更新使第一個用戶的修改失效,那麼時間標 記就會改變,並且當前事務將需要被回滾而不是被提交。對於許多應用程序,中等程度的衝突事務是很少的,因此事務經常會成功完成。

當一個事務失敗時,應用程序把所輸入的數據提交給用戶,使用戶能夠根據導致衝突的更改,做出必要的修改並且重新提交。

其他方面的考慮

將來,使用JDBC3.0特性的開發人員將在更多的方法來優化事務。例如,Oracle9i第2版實施了幾個JDBC3.0特性,包括事務存儲點(Savepoint)。存儲點讓你在一個事務中標記一個點並且回滾它,而不是回滾整個事務。

雖然這聽起來有些難以置信,但存儲點確實能夠極大地減少性能開銷。它們對性能 的實際影響將證明JDBC3.0會得到更廣泛的支持,但不要過多使用存儲點或者避免在任何關鍵性能代碼部分將它們放在一起。如果你確實要使用它們,就必須 確保儘可能地使用Connection.release Savepoint(Savepoint)方法釋放它們的資源。

當前,JDBC2.0支持跨越多個連接的分佈式事務,並且Oracle提供了 一個爲分佈式事務設計的符合業界XA規範的Java Transaction API(JTA)模塊。爲分佈式事務實施一個外部事務管理器的決定並不是一件小事情,然而,分佈式事務比普通事務明顯要慢,因爲它需要額外的通訊工作來協 調多個數據庫資源之間的連接。從純粹的性能觀點來看,最好是在轉移到分佈式事務體系結構以前考慮多種可選擇的設計方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章