日期:2014-05-16  浏览次数:20423 次

JDBC----事务基础讲解(转摘)
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快,但是比