`
mengyang
  • 浏览: 263637 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

BerkeleyDB-JE 事务管理

    博客分类:
  • BDB
阅读更多
本篇开始讲解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中的事务远不止这么简单,它提供了非常的属性来给你配置一个定制的事务。最重要的两个是持久性和一致性的设置
1.持久性
持久性指的是一旦事务提交给了数据库,所有的变化都应该是持久化的,即使应用程序或操作系统发生了错误。
在介绍持久性之前,我们首先讲下在默认情况下当事务提交的时候都发生了什么:
  • 把提交的记录写到日志文件中。
  • 把内存中的日志文件信息写到磁盘上。
  • 释放这个事务所持有的锁。

这里要注意的一点是,JE中的数据时B树结构的,而事务提交的时候,只有位于B树节点的数据才会被写到日志文件中去。其他由事务引起的B树结构改变的数据时不会马上写到日志中的。这个改变只有当以下两种情况时才会写入日志:
1.Environment启动时执行正常恢复(normal recovery)
2.JE有个后台线程会周期性的进行检查点,当然你也可以手动运行检查点。
回过头来说持久性,就像之前讲的,当事务提交的时候改变被同步写到了磁盘日志上,这就保证了数据的持久性。但是有时你可能想降低这个持久性,比如说你为了性能方面的考虑,我们都知道IO操作是很慢的。这可以通过同步策略来进行设置。JE中提供了几种同步策略:
  • Durability.SyncPolicy.SYNC
  • 这是默认的,就是我们上面所讲的,提供了最高的持久性保证。
  • Durability.SyncPolicy.NO_SYNC
  • 这种策略不会让改变写到磁盘中,事务里面所发生的改变全部在JVM中,一旦JVM,应用程序或操作系统发生了错误,改变就找不到了。该策略提供的持久性保证最低,但是却能有非常好的性能。
  • Durability.SyncPolicy.WRITE_NO_SYNC
  • 这种策略会在事务提交的时候把改变写到OS文件缓存中,至于什么时候写到磁盘,是有操作系统决定的。这种策略能保证在JVM发生错误的情况下找到所有已提交的数据,但是操作系统发生错误则数据就找不到了。

持久性策略你可以在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,实际上那两个参数是在复制环境下才有用的。我们等讲到复制环境的那一节时会再次讲解它。
二.隔离性
隔离性保证了在一个事务中处理的数据不会被另外一个事务所修改。隔离性一般是与多线程有关的。JE跟其他的数据库产品一样,也提供了一系列的隔离级别,到时候我们直接选择一种用久可以了,要注意的是,隔离级别越高,所提供的隔离性越强,性能就越差。
在介绍JE中的隔离级别之前,先介绍一些术语
  • 更新丢失(Lost update):两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。
  • 脏读(Dirty Reads):一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚。
  • 不可重复读(Non-repeatable Reads):一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如,在两次读取的中途,有另外一个事务对该行数据进行了修改,并提交。
  • 两次更新问题(Second lost updates problem):无法重复读取的特例。有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。
  • 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

为了解决以上的问题,引入隔离级别的概念
级别术语描述
1读未提交(Read Uncommitted)允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。
2读提交(Read Committed)允许不可重复读取,但不允许脏读取。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
(undefined)可重复读取(Repeatable Read)禁止不可重复读取和脏读取,但是有时可能出现幻影数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
3序列化(Serializable)提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。

默认情况下,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);


接下去我们讲一些小技巧。
自动提交
自动提交可以为你简化代码,只要你设置了事务环境,如果你没有显示的获取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) {

}

要注意的是在一个时间内,你的处理线程只能有一个活动的事务,如果你混淆了显示的事务和自动提交的事务,有可能会引起死锁。
还有一点,游标是不能自动提交的。
带事务的游标
如果你使用了默认的隔离级别,那么当你使用游标每读取一条记录的时候,都会把它锁住,直到整个事务结束。这增加了锁竞争的机会,所以建议降低隔离级别,比如使用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) {

    }

2
3
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics