翻译:陈先波(turbochen@163.com)
日期:2004/7/27
阅读原文:http://www.theserverside.com/articles/article.tss?l=JDBCPerformance_PartIV
July 2004
介绍
开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你.
本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则,这其中的原则已经被许多现有的JDBC应用程序编译运行并验证过。 这些指导原则包括:
以下这些一般性原则可以帮助你解决一些公共的JDBC系统的性能问题.
本节的指导方针将帮助你管理连接与更新,以改善你的JDBC程序的性能。
对于程序性能来说,连接管理很重要. 通过一次连接并执行多次Statement可以优化你的程序,而不要使用多次连接来执行。避免在建立一个初始连接后连到一个数据源
虽然在连接期间获取JDBC驱动的信息是一个好的习惯, 但一次性取得这些信息比两次或多次取得会更有效. 例如, 有些应用程序建立一个连接,然后调用另一个独立模块中的方法去取得JDBC驱动信息. 被设计为独立部分的程序应该传入一个已创建的连接给它而不应该再自己建立一个新的连接.
另一个坏习惯就是你的程序中执行SQL语句时数次的打开和关闭连接. Connection对象应该和多个Statement对象关联. 一个Statement对象,定义了SQL语句在内存中的信息, 能管理多个SQL语句.你可以借助连接池技术显著的改善性能, 对于那些跨网络和互联网的连接特别需要这种技术. 连接池技术可以使连接可以重用. 它关闭连接时并没有关闭与数据库的物理连接. 当程序请求一个连接时, 会自动使用一个活动的连接, 从而避免建立新连接的网络I/O操作.
连接池技术的另一方面, JDBC 3.0也对Statement池作了规定. 跟连接池技术类似, Statement池缓存了PreparedStatement对象,可以在没有程序干预的情况下重用缓存的PreparedStatement对象. 例如, 程序可以会像下面的语句一样建立一个PreparedStatement对象:
select name, address, dept, salary from personnel where empid = ? or name like ? or address = ?"
当PreparedStatement对象被创建时, SQL查询会解析并校验语义,并生成一个optimization plan. 在一些数据库系统中,preparedStatement的创建过程是极其影响性能的,例如DB2. 一但prepared statement一关闭,JDBC 3.0兼容的驱动程序会将这个prepared statement放入本地缓存而不是丢弃它. 如果稍后程序试图使用同一SQL查询建立prepared statement, 这种情况在许多应用程序中都可能发生, 这时驱动程序能简单从本地缓存获取相关的statement而不是透过网络连到服务器让数据库创建.
Connection和Statement在执行之前应该作一些相应处理. 花些时间想一想Connection的管理,以改善程序的性能和可能维护性.
由于磁盘I/O和隐含的网络I/O操作,提交事务是很慢的过程. 应该使用WSConnection.setAutoCommit(false)的方式将Autocommit关闭.
一次提交动作究竟做了什么? 数据库服务器必须将每个数据页中包含了被更新和新增的数据保存到磁盘. 这是一连续的写文件的操作, 说是这么说, 它却是一个磁盘I/O操作. 缺省情况下, 当连接到数据源时,Autocommit是打开的, Autocommit模式通常会削弱性能,因为每次操作会自动提交而引起大量磁盘I/O操作.
此外, 许多数据库并没有提供Autocommit模式. 对于此种类型的数据库, JDBC驱动必须明白,对于每次操作,都要使用一个COMMIT语句和一个BEGIN TRANSACTION句来提交.
虽然使用事务有助于提升性能, 但不要太过分依赖这个技巧. 不用事务可以减少因防止其它用户存取某行而长时间死锁在该行上面.
许多系统支持分布式的事务处理; 这意味着, 事务跨越了多个连接. 分布式事务处理至少比正常的慢四次,因为所有模块之间的调用引起的通讯会引发日志和网络I/O操作(JDBC驱动, 事务监控以及数据库系统). 如非必要,应避免使用分布式事务处理, 而尽可能的使用本地的事务处理方式. 应该注意到一些典型的Java应用服务器已提供了一个缺省的分布式事务处理机制.
要得到最好的系统性能, 设计出来的应用程序应只使用单一的数据库连接.
虽然updateXXX方法没有适用于所有的应用类型, 开发者也应该尝试去使用updateXXX和delete方法. 使用ResultSet对象的updateXXX可以让开发者在不用建立复杂的SQL语句的情况下去更新数据. 开发者只需要简单的提供字段名就行了. 之后,如果要移动光标位置, 要先调用updateRow()方法应用更新.
在下面的代码片断中, 使用了getInt()方法来接收ResultSet对象中的Age字段的值, 并且使用updateInt()方法将年龄值更新为25. 最后调用updateRow()方法将更新应用到数据库.
int n = rs.getInt("Age"); // resultset中可能包含n个Age的值 ... rs.updateInt("Age", 25); rs.updateRow();
除了使应用程序更容易维护之外, 使用updateXXX也可以改善程序的性能. 因为通过Select语句之后数据库服务器已经定位在那一行上, 而不用再次定位要更新的数据而消耗性能, 服务器通常有一个内部指针指向被用到的行(例如, ROWID).
使用getBestRowIdentifier()可以为要更新的数据确定Where子句中的最佳的字段标识符. 通过隐含字段常常能最快的访问到字段, 这些字段标识符只可以通过getBestRowIdentifier()得到.
有些程序无法被设计成使用updateXXX方法. 它们可能会借助getPrimaryKeys()和调用getIndexInfo()找出唯一性索引字段,按一定的规则生成Where子句. 这些方法会导致相当复杂的查询.
看一下下面的例子:
ResultSet WSrs = WSs.executeQuery ("SELECT first_name, last_name, ssn, address, city, state, zip FROM emp"); // 获取数据 ... WSs.executeUpdate ("UPDATE EMP SET ADDRESS = ? WHERE first_name = ? and last_name = ? and ssn = ? and address = ? and city = ? and state = ? and zip = ?"); // 相当复杂的查询
应用程序应该调用getBestRowIdentifier()取得最佳的字段集合(也许是一个隐含字段) 它们标明了特定的记录. 许多数据库支持特殊字段,它们并没有显式的在表中被用户所定义,是表的隐藏字段(例如, ROWID和TID). 这些隐含字段通常提供能最快的存取数据, 因为它们指向了记录的精确位置. 因为隐含字段不是表结构定义的一部分, 它们不可以从getColumns得到. 要确定隐含字段是否存在, 请调用getBestRowIdentifier().
再来看一下前一个例子:
... ResultSet WSrowid = getBestRowIdentifier() (... "emp", ...); ... WSs.executeUpdate ("UPDATE EMP SET ADDRESS = ? WHERE ROWID = ?"; // 最快的存取数据!
如果你的数据源并没有包含隐含字段, 调用getBestRowIdentifier()会得到它的唯一性索引字段(如果这个索引存在的话). 所以, 你的程序不必通过getIndexInfo来查找唯一性索引.
发表于 @ 2004年07月27日 16:37:00|评论(loading...)|编辑