系统性能.
少用Metadata方法
与其它的JDBC方法相比, 由ResultSet对象生成的metadata对象的相对来讲是很慢的. 应用程序应该缓存从ResultSet返回的metadata信息,避免屡次没必要要的执行这个操做.
几乎没有哪个JDBC应用程序不用到metadata,虽然如此,你仍能够经过少用它们来改善系统性能. 要返回JDBC规范规定的结果集的全部列信息, 一个简单的metadata的方法调用可能会使JDBC驱动程序去执行很复杂的查询甚至屡次查询去取得这些数据. 这些细节上的SQL语言的操做是很是消耗性能的.
应用程序应该缓存这些metadata信息. 例如, 程序调用一次getTypeInfo方法后就将这些程序所依赖的结果信息缓存. 而任何程序都不大可能用到这些结果信息中的全部内容,因此这些缓存信息应该是不难维护的.
避免null参数
在metadata的方法中使用null参数或search patterns是很耗时的. 另外, 额外的查询会致使潜在的网络交通的增长. 应尽量的提供一些non-null的参数给metadata方法.
由于metadata的方法很慢, 应用程序要尽量有效的调用它们. 许多应用程序只传递少许的non-null参数给这些方法. 数据库
在第一个getTables()的调用中, 程序可能想知道表'WSTable'是否存在. 固然, JDBC驱动程序会逐个调用它们而且会解译不一样的请求. JDBC驱动程序会解译请求为: 返回全部的表, 视图, 系统表, synonyms, 临时表, 或存在于任何数据库类别任何Schema中的任何别名为'WSTable'的对象.
第二个getTables()的调用会获得更正确的程序想知道的内容. JDBC驱动程序会解译这个请求为: 返回当前数据库类别中全部存在于'johng'这个schema中的全部表.
很显然, JDBC驱动程序处理第二个请求比处理第一个请求更有效率一些.
有时, 你所请求信息中的对象有些信息是已知的. 当调用metadata方法时, 程序能传送到驱动程序的的任何有用信息均可以致使性能和可靠性的改善.
使用'哑元'(dummy)查询肯定表的特性
要避免使用getColumns()去肯定一个表的特性. 而应该使用一个‘哑元’查询来使用getMetadata()方法.
请考虑这样一个程序, 程序中要容许用户选取一些列. 咱们是否应该使用getColumns()去返回列信息给用户仍是以一个'哑元'查询来调用getMetadata()方法呢?
案例 1: GetColumns 方法 缓存
案例 2: GetMetadata 方法 服务器
// 得到了列的完整信息
在这两个案例中, 一个查询被传送到服务器. 但在案例1中, 查询必须被预储和执行, 结果的描述信息必须肯定(以传给getColumns()方法), 而且客户端必须接收一个包含列信息的结果集. 在案例2中, 只要准备一个简单的查询而且只用肯定结果描述信息. 很显然, 案例2执行方式更好一些.
这个讨论有点复杂, 让咱们考虑一个没有本地化支持prepared statement的DBMS服务器. 案例1的性能没有改变, 但案例2中, 由于'哑元'查询必须被执行而不是被预储使得它的性能加强了一些. 由于查询中的WHERE子句老是为FALSE, 查询在不用存取表的数据状况的下会生成没有数据的结果集. 在这种状况下,第二种方式固然比第一种方式好一些.
总而言之,老是使用ResultSet的metadata方法去获取列信息,像列名,列的数据类型,列的数据精度和长度等. 当要求的信息没法从ResultSet的metadata中获取时才去用getColumns()方法(像列的缺省值这些信息等).
获取数据
要有效的获取数据,就只需返回你须要的数据, 以及不少用效的方法. 本节的指导原则将帮助你使用JDB获取数据时优化系统性能.
获取长数据
如非必要, 应用程序不该请求长的数据, 由于长的数据经过网络传输会很是慢和消耗资源.
大多数用户并不想看到大堆的数据. 若是用户不想处理这些长数据, 那么程序应可以再次查询数据库, 在SELECT子句中指定须要的列名. 这种方式容许通常用户获取结果集而不用消耗昂贵的网络流量.
虽然最好的方式是不要将长数据包括在SELECT子句的列名中,但仍是有一些应用程序在发送查询给JDBC驱动程序时并无在SELECT子句中明确指出列名 (确切一点, 有些程序发送这样的查询: select * from
...). 若是SELECT子句的列名中包含长数据, 许多JDBC驱动程序必须在查询时从新获取数据, 甚至在ResultSet中这些长数据并无被程序用到. 在可能状况下,开发者应该试着去实现一种不需获取全部列数据的方法.
例如,看如下的JDBC代码: 网络
要记住JDBC驱动程序没有知觉. 当查询被执行时它不知道哪些列是程序所要的. 驱动程序只知道应用程序能请求任意的列. 当JDBC驱动程序处理 rs.next() 请求时, 它可能会跨过网络从数据库服务器至少返回一行结果. 在这种状况下, 每一个结果行会包含全部的列数据– 若是Employees表有一列包含员工相片的话它也会被包含在结果里面. 限制SELECT子句的列名列表而且只包含有用的列名,会减小网络流量及更快的查询性能.
另外,虽然getClob()和getBlob()方法能够容许应用程序去如何控制获取长数据, 但开发者必须认识到在许多状况下, JDBC驱动程序缺乏真正的LOB定位器的支持. 像这种状况下,在暴露getClob和getBlob方法给开发者以前驱动程序必须通过网络获取全部的长数据.
减小获取的数据量
有时必需要获取长数据. 这时, 要注意的是大多数用户并不想在屏幕上看到100k甚至更多的文字.
要减小网络交通和改善性能, 经过调用setMaxRows(), SetMaxFieldSize及SetFetchSize()方法, 你能够减小取获取的数据量. 另外一种方式是减小数据的列数. 若是驱动程序容许你定义packet的大小, 使用最小的packet尺寸会适合你的须要.
记住: 要当心的返回只有你须要的行和列数据. 当你只须要2列数据而你却返回的5列数据时,性能会下降 – 特别是不须要的行中包含有长数据时.
选择合适的数据类型
接收和发送某些数据可能代价昂贵. 当你设计一个schema时, 应选择能被最有效地处理的数据类型. 例如, 整型数就比浮点数或实数处理起来要快一些. 浮点数的定义是按照数据库的内部规定的格式, 一般是一种压缩格式. 数据必须被 解压和转换到另外种格式, 这样它才能被数据的协议处理.
获取ResultSet
因为数据库系统对可滚动光标的支持有限, 许多JDBC驱动程序并无实现可滚动光标. 除非你确信数据库支持可滚动光标的结果集, 不然不要调用rs.last()和rs.getRow()方法去找出数据集的最大行数. 由于JDBC驱动程序模拟了可滚动光标, 调用rs.last()致使了驱动程序透过网络移到了数据集的最后一行. 取而代之, 你能够用ResultSet遍历一次计数或者用SELECT查询的COUNT函数来获得数据行数.
一般状况下,请不要写那种依赖于结果集行数的代码, 由于驱动程序必须获取全部的数据集以便知道查询会返回多少行数据.
选用JDBC对象和方法
本节的指导原则将帮助你在选用JDBC的对象和方法时哪些会有最好的性能.
在存储过程当中使用参数标记做为参数
当调用存储过程时, 应老是使用参数标记(?)来代替字面上的参数. JDBC驱动能调用数据库的存储过程, 也能被其它的SQL查询执行, 或者直接经过远程进程调用(RPC)的方式执行. 当你将存储过程做为一个SQL查询执行时, 数据库要解析这个查询语句, 校验参数并将参数转换为正确的数据类型.
要记住, SQL语句老是以字符串的形式送到数据库, 例如, “{call getCustName (12345)}”. 在这里, 即便程序中将参数做为整数赋给了getSustName, 而实现上参数仍是以字符串的形式传给了服务器. 数据库会解析这个SQL查询, 而且根据metadata来决定存储过程的参数类型, 而后分解出参数"12345", 而后在最终将存储过程做为一个SQL查询执行以前将字串'12345’转换为整型数.
按RPC方式调用时, 以前那种SQL字符串的方式要避免使用. 取而代之, JDBC驱动会构造一个网络packet, 其中包含了本地数据类型的参数,而后执行远程调用.
案例 1 函数
案例 2 性能
使用Statement而不是PreparedStatement对象
JDBC驱动的最佳化是基于使用的是什么功能. 选择PreparedStatement仍是Statement取决于你要怎么使用它们. 对于只执行一次的SQL语句选择Statement是最好的. 相反, 若是SQL语句被屡次执行选用PreparedStatement是最好的.
PreparedStatement的第一次执行消耗是很高的. 它的性能体如今后面的重复执行. 例如, 假设我使用Employee ID, 使用prepared的方式来执行一个针对Employee表的查询. JDBC驱动会发送一个网络请求到数据解析和优化这个查询. 而执行时会产生另外一个网络请求. 在JDBC驱动中,减小网络通信是最终的目的. 若是个人程序在运行期间只须要一次请求, 那么就使用Statement. 对于Statement, 同一个查询只会产生一次网络到数据库的通信.
对于使用PreparedStatement池的状况下, 本指导原则有点复杂. 当使用PreparedStatement池时, 若是一个查询很特殊, 而且不太会再次执行到, 那么可使用Statement. 若是一个查询不多会被执行,但链接池中的Statement池可能被再次执行, 那么请使用PreparedStatement. 在不是Statement池的一样状况下, 请使用Statement.
使用PreparedStatement的Batch功能
Update大量的数据时, 先Prepare一个INSERT语句再屡次的执行, 会致使不少次的网络链接. 要减小JDBC的调用次数改善性能, 你可使用PreparedStatement的AddBatch()方法一次性发送多个查询给数据库. 例如, 让咱们来比较一下下面的例子.
例 1: 屡次执行Prepared Statement 优化
例 2: 使用Batch spa
ps.executeBatch();
在例 1中, PreparedStatement被用来屡次执行INSERT语句. 在这里, 执行了100次INSERT操做, 共有101次网络往返. 其中,1次往返是预储statement, 另外100次往返执行每一个迭代. 在例2中, 当在100次INSERT操做中使用addBatch()方法时, 只有两次网络往返. 1次往返是预储statement, 另外一次是执行batch命令. 虽然Batch命令会用到更多的数据库的CPU周期, 可是经过减小网络往返,性能获得提升. 记住, JDBC的性能最大的增进是减小JDBC驱动与数据库之间的网络通信.
选择合适的光标类型
选择适用的光标类型以最大限度的适用你的应用程序. 本节主要讨论三种光标类型的性能问题.
对于从一个表中顺序读取全部记录的状况来讲, Forward-Only型的光标提供了最好的性能. 获取表中的数据时, 没有哪一种方法比使用Forward-Only型的光标更快. 但无论怎样, 当程序中必须按无次序的方式处理数据行时, 这种光标就没法使用了.
对于程序中要求与数据库的数据同步以及要可以在结果集中先后移动光标, 使用JDBC的Scroll-Insensitive型光标是较理想的选择. 此类型的光标在第一次请求时就获取了全部的数据(当JDBC驱动采用'lazy'方式获取数据时或许是不少的而不是所有的数据)而且储存在客户端. 所以, 第一次请求会很是慢, 特别是请求长数据时会理严重. 而接下来的请求并不会形成任何网络往返(当使用'lazy'方法时或许只是有限的网络交通) 而且处理起来很快. 由于第一次请求速度很慢, Scroll-Insensitive型光标不该该被使用在单行数据的获取上. 当有要返回长数据时, 开发者也应避免使用Scroll-Insensitive型光标, 由于这样可能会形成内存耗尽. 有些Scroll-Insensitive型光标的实现方式是在数据库的临时表中缓存数据来避免性能问题, 但多数仍是将数据缓存在应用程序中.
Scroll-Sensitive型光标, 有时也称为Keyset-Driven光标, 使用标识符, 像数据库的ROWID之类. 当每次在结果集移动光标时, 会从新该标识符的数据. 由于每次请求都会有网络往返, 性能可能会很慢. 不管怎样, 用无序方式的返回结果行对性能的改善是没有帮助的.
如今来解释一下这个, 来看这种状况. 一个程序要正常的返回1000行数据到程序中. 在执行时或者第一行被请求时, JDBC驱动不会执行程序提供的SELECT语句. 相反, 它会用键标识符来替换SELECT查询, 例如, ROWID. 而后修改过的查询都会被驱动程序执行,跟着会从数据库获取全部1000个键值. 每一次对一行结果的请求都会使JDBC驱动直接从本地缓存中找到相应的键值, 而后构造一个包含了'WHERE ROWID=?'子句的最佳化查询, 再接着执行这个修改过的查询, 最后从服务器取得该数据行.
当程序没法像Scroll-Insensitive型光标同样提供足够缓存时, Scroll-Sensitive型光标能够被替代用来做为动态的可滚动的光标.
使用有效的getter方法
JDBC提供多种方法从ResultSet中取得数据, 像getInt(), getString(), 和getObject()等等. 而getObject()方法是最泛化了的, 提供了最差的性能。 这是由于JDBC驱动必须对要取得的值的类型做额外的处理以映射为特定的对象. 因此就对特定的数据类型使用相应的方法.
要更进一步的改善性能, 应在取得数据时提供字段的索引号, 例如, getString(1), getLong(2), 和getInt(3)等来替代字段名. 若是没有指定字段索引号, 网络交通不会受影响, 但会使转换和查找的成本增长. 例如, 假设你使用getString("foo") ... JDBC驱动可能会将字段名转为大写(若是须要), 而且在到字段名列表中逐个比较来找到"foo"字段. 若是能够, 直接使用字段索引, 将为你节省大量的处理时间.
例如, 假设你有一个100行15列的ResultSet, 字段名不包含在其中. 你感兴趣的是三个字段 EMPLOYEENAME (字串型), EMPLOYEENUMBER (长整型), 和SALARY (整型). 若是你指定getString(“EmployeeName”), getLong(“EmployeeNumber”), 和getInt(“Salary”), 查询旱每一个字段名必须被转换为metadata中相对应的大小写, 而后才进行查找. 若是你使用getString(1), getLong(2), 和getInt(15). 性能就会有显著改善.
获取自动生成的键值
有许多数据库提供了隐藏列为表中的每行记录分配一个惟一键值. 很典型, 在查询中使用这些字段类型是取得记录值的最快的方式, 由于这些隐含列一般反应了数据在磁盘上的物理位置. 在JDBC3.0以前, 应用程序只可在插入数据后经过当即执行一个SELECT语句来取得隐含列的值.
例如:
//插入行 设计
这种取得隐含列的方式有两个主要缺点. 第一, 取得隐含列是在一个独立的查询中, 它要透过网络送到服务器后再执行. 第二, 由于不是主键, 查询条件可能不是表中的惟一性ID. 在后面一个例子中, 可能返回了多个隐含列的值, 程序没法知道哪一个是最后插入的行的值.
(译者:因为不一样的数据库支持的程度不一样,返回rowid的方式各有差别。在SQL Server中,返回最后插入的记录的id能够用这样的查询语句:SELECT @IDENTITY )
JDBC3.0规范中的一个可选特性提供了一种能力, 能够取得刚刚插入到表中的记录的自动生成的键值.
例如: 对象
// 获得生成的键值 如今, 程序中包含了一个惟一性ID, 能够用来做为查询条件来快速的存取数据行, 甚至于表中没有主键的状况也能够.这种取得自动生成的键值的方式给JDBC的开发者提供了灵活性, 而且使存取数据的性能获得提高. 选择合适的数据类型 接收和发送某些数据可能代价昂贵. 当你设计一个schema时, 应选择能被最有效地处理的数据类型. 例如, 整型数就比浮点数或实数处理起来要快一些. 浮点数的定义是按照数据库的内部规定的格式, 一般是一种压缩格式. 数据必须被 解压和转换到另外种格式, 这样它才能被数据的协议处理. 获取ResultSet 因为数据库系统对可滚动光标的支持有限, 许多JDBC驱动程序并无实现可滚动光标. 除非你确信数据库支持可滚动光标的结果集, 不然不要调用rs.last()和rs.getRow()方法去找出数据集的最大行数. 由于JDBC驱动程序模拟了可滚动光标, 调用rs.last()致使了驱动程序透过网络移到了数据集的最后一行. 取而代之, 你能够用ResultSet遍历一次计数或者用SELECT查询的COUNT函数来获得数据行数. 一般状况下,请不要写那种依赖于结果集行数的代码, 由于驱动程序必须获取全部的数据集以便知道查询会返回多少行数据.