BerkeleyDB-JE 事務管理

本篇開始講解BerkeleyDB的事務管理
顯然,作爲一個成熟的數據庫產品,都必須提供事務機制來保證數據的ACID特性。我們之前講的BerkeleyDB都沒有在事務環境中進行操作。現在我們開始講解如何使用事務,以及配置事務的各種特性。
實現一個最簡單的事務系統,有幾個步驟:
1.配置Environment環境支持事務
2.獲取一個事務句柄
3.提交或回滾事務

File envHome = new File("E:\je");
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setTransactional(true);
Environment environment = new Environment(envHome, envConfig);

Transaction txn = null;
try {
txn = environment.beginTransaction(null, null);
doTransactionWork(txn);
txn.commit();
} catch (LockConflictException lockConflict) {
txn.abort();
}

以上是一個很簡單的框架代碼,你在doTransactionWork方法中的操作現在都在一個事務中了。
當然BerkeleyDB中的事務遠不止這麼簡單,它提供了非常的屬性來給你配置一個定製的事務。最重要的兩個是持久性和一致性的設置
[b]1.持久性[/b]
持久性指的是一旦事務提交給了數據庫,所有的變化都應該是持久化的,即使應用程序或操作系統發生了錯誤。
在介紹持久性之前,我們首先講下在默認情況下當事務提交的時候都發生了什麼:
[list]
[*]把提交的記錄寫到日誌文件中。
[*]把內存中的日誌文件信息寫到磁盤上。
[*]釋放這個事務所持有的鎖。
[/list]
這裏要注意的一點是,JE中的數據時B樹結構的,而事務提交的時候,只有位於B樹節點的數據纔會被寫到日誌文件中去。其他由事務引起的B樹結構改變的數據時不會馬上寫到日誌中的。這個改變只有當以下兩種情況時纔會寫入日誌:
1.Environment啓動時執行正常恢復(normal recovery)
2.JE有個後臺線程會週期性的進行檢查點,當然你也可以手動運行檢查點。
回過頭來說持久性,就像之前講的,當事務提交的時候改變被同步寫到了磁盤日誌上,這就保證了數據的持久性。但是有時你可能想降低這個持久性,比如說你爲了性能方面的考慮,我們都知道IO操作是很慢的。這可以通過同步策略來進行設置。JE中提供了幾種同步策略:
[list]
[*]Durability.SyncPolicy.SYNC
這是默認的,就是我們上面所講的,提供了最高的持久性保證。
[*]Durability.SyncPolicy.NO_SYNC
這種策略不會讓改變寫到磁盤中,事務裏面所發生的改變全部在JVM中,一旦JVM,應用程序或操作系統發生了錯誤,改變就找不到了。該策略提供的持久性保證最低,但是卻能有非常好的性能。
[*]Durability.SyncPolicy.WRITE_NO_SYNC
這種策略會在事務提交的時候把改變寫到OS文件緩存中,至於什麼時候寫到磁盤,是有操作系統決定的。這種策略能保證在JVM發生錯誤的情況下找到所有已提交的數據,但是操作系統發生錯誤則數據就找不到了。
[/list]
持久性策略你可以在Environment級別設置,也可以在事務級別設置,在事務級別設置的策略會覆蓋Environment級別的設置。

EnvironmentConfig envConfig = new EnvironmentConfig();
//設置持久性策略
Durability durability = new Durability(SyncPolicy.WRITE_NO_SYNC, null, null);
envConfig.setDurability(durability);
environment = new Environment(envHome, envConfig);
...
TransactionConfig txnConfig = new TransactionConfig();
Durability durability = new Durability(SyncPolicy.NO_SYNC, null, null);
txnConfig.setDurability(durability);
Transaction txn = null;
try {
txn = environment.beginTransaction(null, txnConfig);
doTransactionWork(txn);
txn.commit();
} catch (LockConflictException lockConflict) {
txn.abort();
}

也許有人會注意到Durability構造函數的後兩個參數都爲null,實際上那兩個參數是在複製環境下才有用的。我們等講到複製環境的那一節時會再次講解它。
[b]二.隔離性[/b]
隔離性保證了在一個事務中處理的數據不會被另外一個事務所修改。隔離性一般是與多線程有關的。JE跟其他的數據庫產品一樣,也提供了一系列的隔離級別,到時候我們直接選擇一種用久可以了,要注意的是,隔離級別越高,所提供的隔離性越強,性能就越差。
在介紹JE中的隔離級別之前,先介紹一些術語
[list]
[*]更新丟失(Lost update):兩個事務都同時更新一行數據,但是第二個事務卻中途失敗退出,導致對數據的兩個修改都失效了。這是因爲系統沒有執行任何的鎖操作,因此併發事務並沒有被隔離開來。
[*]髒讀(Dirty Reads):一個事務開始讀取了某行數據,但是另外一個事務已經更新了此數據但沒有能夠及時提交。這是相當危險的,因爲很可能所有的操作都被回滾。
[*]不可重複讀(Non-repeatable Reads):一個事務對同一行數據重複讀取兩次,但是卻得到了不同的結果。例如,在兩次讀取的中途,有另外一個事務對該行數據進行了修改,並提交。
[*]兩次更新問題(Second lost updates problem):無法重複讀取的特例。有兩個併發事務同時讀取同一行數據,然後其中一個對它進行修改提交,而另一個也進行了修改提交。這就會造成第一次寫操作失效。
[*]幻讀(Phantom Reads):事務在操作過程中進行兩次查詢,第二次查詢的結果包含了第一次查詢中未出現的數據(這裏並不要求兩次查詢的SQL語句相同)。這是因爲在兩次查詢過程中有另外一個事務插入數據造成的。
[/list]
爲了解決以上的問題,引入隔離級別的概念
[table]
|級別|術語|描述|
|1|讀未提交(Read Uncommitted)|允許髒讀取,但不允許更新丟失。如果一個事務已經開始寫數據,則另外一個數據則不允許同時進行寫操作,但允許其他事務讀此行數據。|
|2|讀提交(Read Committed)|允許不可重複讀取,但不允許髒讀取。讀取數據的事務允許其他事務繼續訪問該行數據,但是未提交的寫事務將會禁止其他事務訪問該行。 |
|(undefined)|可重複讀取(Repeatable Read)|禁止不可重複讀取和髒讀取,但是有時可能出現幻影數據。讀取數據的事務將會禁止寫事務(但允許讀事務),寫事務則禁止任何其他事務。 |
|3|序列化(Serializable)|提供嚴格的事務隔離。它要求事務序列化執行,事務只能一個接着一個地執行,但不能併發執行。|
[/table]
默認情況下,JE事務是提供了可重複讀取(Repeatable Read)的級別,你可以降低或提高事務的隔離級別。這些隔離級別可以在TransactionConfig中進行設置:

TransactionConfig txnConfig = new TransactionConfig();
txnConfig.setReadUncommitted(true);
txnConfig.setReadCommitted(true);
txnConfig.setSerializableIsolation(true);

其中序列化的隔離級別你還可以在Environment中設置,這樣會是所有的事務默認使用序列化的級別:

EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setTxnSerializableIsolation(true);


接下去我們講一些小技巧。
[b]自動提交[/b]
自動提交可以爲你簡化代碼,只要你設置了事務環境,如果你沒有顯示的獲取Transaction Handle,在你調用Database或者EntityStore進行一個單獨的寫操作的時候,會自動的被一個事務給包圍起來並且爲你執行提交或回滾。

Database myDatabase = null;
Environment myEnv = null;
try {
EnvironmentConfig myEnvConfig = new EnvironmentConfig();
myEnvConfig.setTransactional(true);
myEnv = new Environment(new File("/my/env/home"),
myEnvConfig);

DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setTransactional(true);
myDatabase = myEnv.openDatabase(null,
"sampleDatabase",
dbConfig);
String keyString = "thekey";
String dataString = "thedata";
DatabaseEntry key =
new DatabaseEntry(keyString.getBytes("UTF-8"));
DatabaseEntry data =
new DatabaseEntry(dataString.getBytes("UTF-8"));

myDatabase.put(null, key, data);
} catch (DatabaseException de) {

}

要注意的是在一個時間內,你的處理線程只能有一個活動的事務,如果你混淆了顯示的事務和自動提交的事務,有可能會引起死鎖。
還有一點,遊標是不能自動提交的。
[b]帶事務的遊標[/b]
如果你使用了默認的隔離級別,那麼當你使用遊標每讀取一條記錄的時候,都會把它鎖住,直到整個事務結束。這增加了鎖競爭的機會,所以建議降低隔離級別,比如使用read committed。你可以設置整個事務或者事務下某個遊標的隔離級別,這樣可以把隔離級別應用於事務下的全部遊標或者是某個遊標。

//打開一個事務,使用默認的隔離級別REPEATABLE READ
Transaction txn = myEnv.beginTransaction(null, null);
Cursor cursor = null;
try {
//打開一個帶有事務的遊標,
//同時設置該遊標使用低級的隔離級別READ UNCOMMITTED
CursorConfig cconfig = new CursorConfig();
cconfig.setReadUncommitted(true);
cursor = db.openCursor(txn, cconfig);
...
}catch (DatabaseException de) {

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