为环境和数据库启用事务后,可以使用它们来保护数据库操作。 您可以通过获取事务句柄然后将该句柄用于要参与该事务的任何数据库操作来执行此操作。
您使用Environment.beginTransaction()方法获取事务句柄。
完成要包含在事务中的所有操作后,必须使用Transaction.commit()方法提交事务。
如果出于任何原因,您想要放弃该事务,则使用Transaction.abort()将其中止。
任何已提交或已中止的事务句柄都不能再由您的应用程序使用。
最后,在关闭数据库和环境之前,必须确保提交或中止所有事务句柄。
如果您只想事务保护单个数据库写操作,则可以使用自动提交来执行事务管理。 使用自动提交时,不需要显式事务句柄。 有关更多信息,请参阅自动提交
例如,以下示例打开启用事务的环境和存储,获取事务句柄,然后在其保护下执行写入操作。 如果写入操作失败,则事务中止并且存储处于一种状态,就好像首先没有尝试过任何操作一样。
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.StoreConfig;
import java.io.File;
...
Environment myEnv = null;
EntityStore store = null;
// Our convenience data accessor class, used for easy access to
// EntityClass indexes.
DataAccessor da;
try {
EnvironmentConfig myEnvConfig = new EnvironmentConfig();
myEnvConfig.setTransactional(true);
myEnv = new Environment(new File("/my/env/home"),
myEnvConfig);
StoreConfig storeConfig = new StoreConfig();
storeConfig.setTransactional(true);
EntityStore store = new EntityStore(myEnv,
"EntityStore", storeConfig);
da = new DataAccessor(store);
// Assume that Inventory is an entity class.
Inventory theInventory = new Inventory();
theInventory.setItemName("Waffles");
theInventory.setItemSku("waf23rbni");
Transaction txn = myEnv.beginTransaction(null, null);
try {
// Put the object to the store using the transaction handle.
da.inventoryBySku.put(txn, theInventory);
// Commit the transaction. The data is now safely written to the
// store.
txn.commit();
// If there is a problem, abort the transaction
} catch (Exception e) {
if (txn != null) {
txn.abort();
txn = null;
}
}
} catch (DatabaseException de) {
// Exception handling goes here
}
基础API也可以做同样的事情; 如果写入操作失败,则使用的数据库保持不变:
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import java.io.File;
...
Database myDatabase = null;
Environment myEnv = null;
try {
EnvironmentConfig myEnvConfig = new EnvironmentConfig();
myEnvConfig.setTransactional(true);
myEnv = new Environment(new File("/my/env/home"),
myEnvConfig);
// Open the database. Create it if it does not already exist.
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"));
Transaction txn = myEnv.beginTransaction(null, null);
try {
myDatabase.put(txn, key, data);
txn.commit();
} catch (Exception e) {
if (txn != null) {
txn.abort();
txn = null;
}
}
} catch (DatabaseException de) {
// Exception handling goes here
}
3.1 Committing a Transaction 提交一个事务
为了完全理解提交事务时发生的事情,您必须先了解一下JE对其日志文件的处理方式。 日志记录会导致在日志文件中标识所有数据库或存储写入操作(请记住,在JE中,您的日志文件是您的数据库文件;两者之间没有区别)。 编写足够的信息以在系统或应用程序发生故障时恢复整个BTree,因此通过执行日志记录,JE可确保数据的完整性。
请记住,对您的数据库或Store所做的所有写入活动都在JE的日志中标识,因为您的应用程序会执行写入操作。 但是,JE维护内存中的日志。 最终将此信息写入磁盘,但特别是在事务性应用程序的情况下,此数据可能会保留在内存中,直到提交事务,或者JE用尽日志信息的缓冲区空间。
提交事务时,会发生以下情况:
- 提交记录将写入日志。 这表明交易所做的修改现在是永久性的。
默认情况下,此写操作与磁盘同步执行,因此在执行任何其他操作之前,提交记录将到达日志文件中。 保存在内存中的任何日志信息(默认情况下)都会同步写入磁盘。 请注意,此要求可以放宽,具体取决于您执行的提交类型。
有关详细信息,请参阅非持久性事务。
请注意,事务提交仅将BTree的叶节点写入JE的日志文件。 所有其他内部BTree结构都是不成文的。事务持有的所有锁都被释放。
这意味着由其他事务或控制线程执行的读取操作现在可以查看修改而无需借助未提交的读取(有关更多信息,请参阅读取未提交的数据)。
要提交事务,只需调用Transaction.commit()即可。
请记住,事务提交只会将BTree叶节点写入JE的日志文件。 由于事务的活动而对BTree进行的任何其他修改都不会写入日志文件。 这意味着随着时间的推移,JE的正常恢复时间可能会大大增加(请记住,JE在打开环境时始终可以正常恢复)。
因此,JE默认运行checkpointer线程。 此后台线程以定期间隔运行检查点,以确保最小化在环境打开时需要恢复的数据量。 此外,您还可以手动运行检查点。 有关更多信息,请参阅检查点。
请注意,一旦提交了事务,您用于事务的事务句柄就不再有效。 要在新事务的控制下执行数据库活动,您必须获取新的事务句柄。
3.2 Non-Durable Transactions 非持久事务
如前所述,默认情况下,事务提交是持久的,因为它们会导致在事务下执行的修改同步记录在磁盘上的日志文件中。但是,可以使用非持久性事务。
出于性能原因,您可能需要非持久性事务。例如,您可能只是将事务用于隔离保证。在这种情况下,您可能希望放松JE通常作为事务提交的一部分执行的同步写入磁盘。这样做意味着您的数据仍然会进入磁盘;但是,您的应用程序不一定必须等待磁盘I / O完成才能执行其他数据库操作。这可以极大地提高某些工作负载的吞吐量。
要放宽事务的持久性保证,可以使用Durability类来定义要使用的持久性策略。 Durability类构造函数接受三个参数,其中只有一个对于独立事务应用程序很有用:
- 本地计算机的同步策略。
- 副本的同步策略。 仅用于JE HA应用程序。
- 确认政策。 同样,这仅适用于JE HA应用程序
我们在Berkeley DB,Java版高可用性应用程序入门指南中描述了JE高可用性应用程序。
您为Durability类构造函数提供的同步策略可以是以下之一:
- Durability.SyncPolicy.SYNC
在事务提交时写入并同步刷新日志到磁盘。这提供了最持久的事务配置,因为在完成所有磁盘I / O之前,提交操作不会返回。但是,相反,这提供了更糟糕的写入性能,因为磁盘I / O是一项昂贵且耗时的操作。
这是默认的同步策略。使用此策略的事务被认为是持久的。
- Durability.SyncPolicy.NO_SYNC
这会导致JE在事务提交时不会同步强制任何数据到磁盘。也就是说,修改完全保存在JVM内部,并且不会强制修改文件系统以进行长期存储。但请注意,数据最终将作为JE管理其日志记录缓冲区和/或缓存的一部分进入文件系统(假设没有应用程序或操作系统崩溃)。
这种形式的提交提供了弱持久性保证,因为数据丢失可能由于应用程序,JVM或操作系统崩溃而发生。实际上,这代表了您可以为事务提供的最不耐用的配置。但它也提供了比其他选项更好的写入性能。
- Durability.SyncPolicy.WRITE_NO_SYNC
这会导致数据在事务提交时同步写入操作系统的文件系统缓冲区。数据最终将写入磁盘,但这在操作系统选择安排活动时会发生;在OS执行此磁盘I / O之前,事务提交可以成功完成。
这种提交形式可以保护您免受应用程序和JVM崩溃的影响,但不能防止操作系统崩溃。与NO_SYNC相比,此方法提供的数据丢失可能性较小。
您可以通过创建Durability类然后将其提供给EnvironmentConfig.setDurability()来在整个环境范围内指定持久性策略。 您还可以通过为TransactionConfig对象提供Durability类来逐个事务地覆盖环境默认持久性策略,该对象用于使用TransactionConfig.setDurability()方法配置事务。
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Durability;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import java.io.File;
...
Database myDatabase = null;
Environment myEnv = null;
try {
Durability defaultDurability =
new Durability(Durability.SyncPolicy.NO_SYNC,
null, // unused by non-HA applications.
null); // unused by non-HA applications.
EnvironmentConfig myEnvConfig = new EnvironmentConfig();
myEnvConfig.setTransactional(true);
myEnvConfig.setDurability(defaultDurability);
myEnv = new Environment(new File("/my/env/home"),
myEnvConfig);
// Open the database. Create it if it does not already exist.
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"));
Durability newDurability =
new Durability(Durability.SyncPolicy.WRITE_NO_SYNC,
null, // unused by non-HA applications.
null); // unused by non-HA applications.
TransactionConfig tc = new TransactionConfig();
tc.setDurability(newDurability);
Transaction txn = myEnv.beginTransaction(null, tc);
try {
myDatabase.put(txn, key, data);
txn.commit();
} catch (Exception e) {
if (txn != null) {
txn.abort();
txn = null;
}
}
} catch (DatabaseException de) {
// Exception handling goes here
}
3.3 Aborting a Transaction 终止一个事务
中止事务时,将丢弃在事务保护下执行的所有数据库或存储修改,并释放事务当前持有的所有锁。 在这种情况下,您的数据只是处于事务开始执行数据修改之前的状态。
中止事务后,用于事务的事务句柄不再有效。 要在新事务的控制下执行数据库活动,您必须获取新的事务处理句柄。
要中止事务,请调用Transaction.abort()
3.4 Auto Commit 自动提交
虽然事务经常用于为多个数据库或存储操作提供原子性,但有时需要在事务的控制下执行单个数据库或存储操作。 而不是强制您获取事务,执行单个写操作,然后提交或中止事务,您可以使用自动提交自动分组此事件序列。
要使用自动提交:
- 打开您的环境和数据库或存储,以便它们支持事务。
- 不要为执行数据库或存储写入操作的方法提供事务处理。
请注意,游标不能使用自动提交。 如果希望游标的操作受事务保护,则必须始终使用事务打开游标。 有关使用事务性游标的详细信息,请参阅事务性游标。
一次不要在您的线程中有多个活动事务。 如果将显式事务与使用自动提交的其他操作混合,则尤其会出现问题。 这样做可能导致无法检测到的死锁。
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import java.io.File;
...
Database myDatabase = null;
Environment myEnv = null;
try {
EnvironmentConfig myEnvConfig = new EnvironmentConfig();
myEnvConfig.setTransactional(true);
myEnv = new Environment(new File("/my/env/home"),
myEnvConfig);
// Open the database. Create it if it does not already exist.
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"));
// Perform the write. Because the database was opened to
// support transactions, this write is performed using auto commit.
myDatabase.put(null, key, data);
} catch (DatabaseException de) {
// Exception handling goes here
}
3.5 Transactional Cursors 事务游标
您可以通过在创建游标时指定事务句柄来事务保护游标操作。 除此之外,您不会直接向游标方法提供事务句柄。
请注意,如果事务保护游标,则必须确保在提交或中止事务之前关闭游标。 例如:
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import java.io.File;
...
Database myDatabase = null;
Environment myEnv = null;
try {
// Database and environment opens omitted
String replacementData = "new data";
Transaction txn = myEnv.beginTransaction(null, null);
Cursor cursor = null;
try {
// Use the transaction handle here
cursor = db.openCursor(txn, null);
DatabaseEntry key, data;
DatabaseEntry key, data;
while(cursor.getNext(key, data, LockMode.DEFAULT) ==
OperationStatus.SUCCESS) {
data.setData(replacementData.getBytes("UTF-8"));
// No transaction handle is used on the cursor read or write
// methods.
cursor.putCurrent(data);
}
cursor.close();
cursor = null;
txn.commit();
} catch (Exception e) {
if (cursor != null) {
cursor.close();
}
if (txn != null) {
txn.abort();
txn = null;
}
}
} catch (DatabaseException de) {
// Exception handling goes here
}
3.5.1 Using Transactional DPL Cursors 使用事务DPL游标
使用DPL时,使用实体类的主索引或辅助索引创建游标(有关详细信息,请参阅“Berkeley DB入门”,Java版本指南)。 在创建游标时,将事务句柄传递给entities()方法,这会导致使用该游标执行的所有后续操作都在事务范围内执行。
请注意,如果您使用的是启用事务的存储,则必须在打开游标时提供事务句柄。
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.Transaction;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import java.io.File;
...
Environment myEnv = null;
EntityStore store = null;
...
// Store and environment open omitted, as is the DataAccessor
// instantiation.
...
Transaction txn = myEnv.beginTransaction(null, null);
PrimaryIndex<String,Inventory> pi =
store.getPrimaryIndex(String.class, Inventory.class);
EntityCursor<Inventory> pi_cursor = pi.entities(txn, null);
try {
for (Inventory ii : pi_cursor) {
// do something with each object "ii"
// A transactional handle is not required for any write
// operations. All operations performed using this cursor
// will be done within the scope of the transaction, txn.
}
pi_cursor.close();
pi_cursor = null;
txn.commit();
txn = null;
// Always make sure the cursor is closed when we are done with it.
} catch (Exception e) {
if (pi_cursor != null) {
pi_cursor.close();
}
if (txn != null) {
txn.abort();
txn = null;
}
}
3.6 Secondary Indices with Transaction Applications 二级索引使用事务
只要打开二级索引以使其成为事务性索引,就可以将事务与二级索引一起使用。
将二级索引与事务一起使用的所有其他方面与使用没有事务的二级索引相同。 此外,事务保护辅助游标的执行与保护普通游标一样 - 您只需确保使用事务句柄打开游标,并且在句柄提交或中止之前关闭游标。 有关详细信息,请参阅事务性游标。
请注意,当您使用事务来保护数据库写入时,会保护您的辅助索引免受损坏,因为主要和辅助索引的更新是在单个原子事务中执行的。
注意
如果您正在使用DPL,那么请注意,在打开索引时,您无需提供事务处理句柄,无论是主索引还是辅助索引。 但是,如果为您的Store启用了事务,则将打开您打开的所有索引以进行事务使用。 此外,使用该索引执行的任何写操作都将使用事务完成,无论您是否为写操作显式提供事务处理。
如果没有为事务存储上执行的DPL写操作显式提供事务句柄,则自动提交将自动用于该操作。
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseType;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.SecondaryConfig;
import java.io.FileNotFoundException;
...
// Environment and primary database opens omitted.
SecondaryConfig mySecConfig = new SecondaryConfig();
mySecConfig.setAllowCreate(true);
mySecConfig.setTransactional(true);
SecondaryDatabase mySecDb = null;
try {
// A fake tuple binding that is not actually implemented anywhere.
// The tuple binding is dependent on the data in use.
// See the Getting Started Guide for details
TupleBinding myTupleBinding = new MyTupleBinding();
// Open the secondary. FullNameKeyCreator is not actually implemented
// anywhere. See the Getting Started Guide for details.
FullNameKeyCreator keyCreator =
new FullNameKeyCreator(myTupleBinding);
// Set the key creator on the secondary config object.
mySecConfig.setKeyCreator(keyCreator);
// Perform the actual open. Because this database is configured to be
// transactional, the open is automatically wrapped in a transaction.
// - myEnv is the environment handle.
// - myDb is the primary database handle.
String secDbName = "mySecondaryDatabase";
mySecDb = myEnv.openSecondary(null, secDbName, null, myDb,
mySecConfig);
} catch (DatabaseException de) {
// Exception handling goes here ...
}
3.7 Configuring the Transaction Subsystem 配置事务子系统
配置事务子系统时,需要考虑事务超时值。此值表示事务可以处于活动状态的最长时间。但请注意,仅当JE检查其锁定表是否存在阻塞锁时才会检查事务超时(有关详细信息,请参阅锁定,块和死锁)。因此,事务的超时可能已过期,但在JE有理由检查其锁定表之前,不会通知应用程序。
请注意,在交易有机会完成之前,某些交易可能会被不适当地超时。因此,只有当您知道应用程序可能具有不可接受的长事务并且您希望确保应用程序在执行期间不会停止时,才应使用此机制。 (例如,如果您的事务阻塞或请求太多数据,则可能会发生这种情况。)
请注意,默认情况下,事务超时设置为0秒,这意味着它们永远不会超时。
要设置事务的最大超时值,请使用EnvironmentConfig.setTxnTimeout()方法。此方法配置整个环境;不仅仅是用于设置配置的句柄。此外,可以在应用程序的生命周期中的任何时间设置该值。
也可以使用JE属性文件中的je.txn.timeout属性设置此值。
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import java.io.File;
...
Environment myEnv = null;
try {
EnvironmentConfig myEnvConfig = new EnvironmentConfig();
myEnvConfig.setTransactional(true);
// Configure a maximum transaction timeout of 1 second.
myEnvConfig.setTxnTimeout(1000000);
myEnv = new Environment(new File("/my/env/home"),
myEnvConfig);
// From here, you open your databases (or store), proceed with your
// database or store operations, and respond to deadlocks as is
// normal (omitted for brevity).
...