天天看点

BerkeleyDB-JE 事务管理

本篇开始讲解BerkeleyDB的事务管理

显然,作为一个成熟的数据库产品,都必须提供事务机制来保证数据的ACID特性。我们之前讲的BerkeleyDB都没有在事务环境中进行操作。现在我们开始讲解如何使用事务,以及配置事务的各种特性。

实现一个最简单的事务系统,有几个步骤:

1.配置Environment环境支持事务

2.获取一个事务句柄

3.提交或回滚事务

以上是一个很简单的框架代码,你在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级别的设置。

也许有人会注意到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中进行设置:

其中序列化的隔离级别你还可以在Environment中设置,这样会是所有的事务默认使用序列化的级别:

接下去我们讲一些小技巧。

[b]自动提交[/b]

自动提交可以为你简化代码,只要你设置了事务环境,如果你没有显示的获取Transaction Handle,在你调用Database或者EntityStore进行一个单独的写操作的时候,会自动的被一个事务给包围起来并且为你执行提交或回滚。

要注意的是在一个时间内,你的处理线程只能有一个活动的事务,如果你混淆了显示的事务和自动提交的事务,有可能会引起死锁。

还有一点,游标是不能自动提交的。

[b]带事务的游标[/b]

如果你使用了默认的隔离级别,那么当你使用游标每读取一条记录的时候,都会把它锁住,直到整个事务结束。这增加了锁竞争的机会,所以建议降低隔离级别,比如使用read committed。你可以设置整个事务或者事务下某个游标的隔离级别,这样可以把隔离级别应用于事务下的全部游标或者是某个游标。

继续阅读