当前位置:天才代写 > tutorial > JAVA 教程 > 优化JDBC性能的三大技巧

优化JDBC性能的三大技巧

2017-11-10 08:00 星期五 所属: JAVA 教程 浏览:471

副标题#e#

开拓一个注重机能的JDBC应用措施不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动措施并不会抛出异常汇报你。

本系列的机能提示将为改进JDBC应用措施的机能先容一些根基的指导原则,这个中的原则已经被很多现有的JDBC应用措施编译运行并验证过。 这些指导原则包罗:

正确的利用数据库MetaData要领

只获取需要的数据

选用最佳机能的成果

打点毗连和更新

以下这些一般性原则可以辅佐你办理一些民众的JDBC系统的机能问题.

利用数据库Metadata要领

因为通过ResultSet工具生成的Metadata要领与其它的JDBCB要领对比是较慢的, 常常的利用它们将会减弱系统的的机能. 本节的指导原则将辅佐你选择和利用meatdata时优化系统机能.

罕用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参数给这些要领.

譬喻:

ResultSet WSrs = WSc.getTables (null, null, "WSTable", null);

应该这样:

ResultSet WSrs = WSc.getTables ("cat1", "johng", "WSTable", "TABLE");


#p#副标题#e#

在第一个getTables()的挪用中, 措施大概想知道表’WSTable’是否存在. 虽然, JDBC驱动措施会逐个挪用它们而且会解译差异的请求. JDBC驱动措施会解译请求为: 返回所有的表, 视图, 系统表, synonyms, 姑且表, 或存在于任何数据库种别任何Schema中的任何别名为’WSTable’的工具.

第二个getTables()的挪用会获得矫正确的措施想知道的内容. JDBC驱动措施会解译这个请求为: 返回当前数据库种别中所有存在于’johng’这个schema中的所有表.

很显然, JDBC驱动措施处理惩罚第二个请求比处理惩罚第一个请求更有效率一些.

有时, 你所请求信息中的工具有些信息是已知的. 当挪用metadata要领时, 措施能传送到驱动措施的的任何有用信息都可以导致机能和靠得住性的改进.

利用’哑元'(dummy)查询确定表的特性

要制止利用getColumns()去确定一个表的特性. 而应该利用一个‘哑元’查询来利用getMetadata()要领.

请思量这样一个措施, 措施中要答允用户选取一些列. 我们是否应该利用getColumns()去返回列信息给用户照旧以一个’哑元’查询来挪用getMetadata()要领呢?

案例 1: GetColumns 要领

ResultSet WSrc = WSc.getColumns (... "UnknownTable" ...);
// getColumns()会发出一个查询给数据库系统
. . .
WSrc.next();
string Cname = getString(4);
. . .
// 用户必需从重复从处事器获取N行数据
// N = UnknownTable的列数

案例 2: GetMetadata 要领

// 筹备'哑元'查询
PreparedStatement WSps = WSc.prepareStatement
("SELECT * from UnknownTable WHERE 1 = 0");
// 查询从来没有被执行,只是被预储
ResultSetMetaData WSsmd=WSps.getMetaData();
int numcols = WSrsmd.getColumnCount();
...
int ctype = WSrsmd.getColumnType(n)
...
// 得到了列的完整信息

在这两个案例中, 一个查询被传送随处事器. 但在案例1中, 查询必需被预储和执行, 功效的描写信息必需确定(以传给getColumns()要领), 而且客户端必需吸收一个包括列信息的功效集. 在案例2中, 只要筹备一个简朴的查询而且只用确定功效描写信息. 很显然, 案例2执行方法更好一些.

这个接头有点巨大, 让我们思量一个没有当地化支持prepared statement的DBMS处事器. 案例1的机能没有改变, 但案例2中, 因为’哑元’查询必需被执行而不是被预储使得它的机能加强了一些. 因为查询中的WHERE子句老是为FALSE, 查询在不消存取表的数据环境的下会生成没有数据的功效集. 在这种环境下,第二种方法虽然比第一种方法好一些.

#p#分页标题#e#

总而言之,老是利用ResultSet的metadata要领去获取列信息,像列名,列的数据范例,列的数据精度和长度等. 当要求的信息无法从ResultSet的metadata中获取时才去用getColumns()要领(像列的缺省值这些信息等)。

#p#副标题#e#

获取数据

要有效的获取数据,就只需返回你需要的数据, 以及许多用效的要领。本节的指导原则将辅佐你利用JDB获取数据时优化系统机能。

获取长数据

如非须要, 应用措施不该请求长的数据, 因为长的数据通过网络传输会很是慢和耗损资源。

大大都用户并不想看到大堆的数据。假如用户不想处理惩罚这些长数据, 那么措施应可以或许再次查询数据库, 在SELECT子句中指定需要的列名。这种方法答允一般用户获取功效集而不消耗损昂贵的网络流量。

固然最好的方法是不要将长数据包罗在SELECT子句的列名中,但照旧有一些应用措施在发送查询给JDBC驱动措施时并没有在SELECT子句中明晰指出列名 (确切一点, 有些措施发送这样的查询: select * from <table name> …). 假如SELECT子句的列名中包括长数据, 很多JDBC驱动措施必需在查询时从头获取数据, 甚至在ResultSet中这些长数据并没有被措施用到. 在大概环境下,开拓者应该试着去实现一种不需获取所有列数据的要领.

譬喻,看以下的JDBC代码:

ResultSet rs = stmt.executeQuery (
"select * from Employees where SSID = '999-99-2222'");
rs.next();
string name = rs.getString (4);

要记着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的工具和要领时哪些会有最好的机能。

#p#副标题#e#

在存储进程中利用参数标志作为参数

当挪用存储进程时, 应老是利用参数标志(?)来取代字面上的参数。JDBC驱动能挪用数据库的存储进程, 也能被其它的SQL查询执行, 可能直接通过长途历程挪用(RPC)的方法执行。当你将存储进程作为一个SQL查询执行时, 数据库要理会这个查询语句, 校验参数并将参数转换为正确的数据范例。

要记着, SQL语句老是以字符串的形式送到数据库, 譬喻, “{call getCustName (12345)}”。在这里, 纵然措施中将参数作为整数赋给了getSustName, 而实现上参数照旧以字符串的形式传给了处事器。数据库会理会这个SQL查询, 而且按照metadata来抉择存储进程的参数范例, 然后解析出参数"12345", 然后在最终将存储进程作为一个SQL查询执行之前将字串’12345’转换为整型数。

#p#分页标题#e#

按RPC方法挪用时, 之前那种SQL字符串的方法要制止利用。取而代之, JDBC驱动会结构一个网络packet, 个中包括了当地数据范例的参数,然后执行长途挪用。

案例 1

在这个例子中, 存储进程不能最佳的利用RPC。数据库必需将这作为一个普通的语言来举办理会,校验参数范例并将参数转换为正确的数据范例,最后才执行这个存储进程。

CallableStatement cstmt = conn.prepareCall (
"{call getCustName (12345)}");
ResultSet rs = cstmt.executeQuery ();

案例 2

在这个例子中, 存储进程能最佳的执行RPC。因为措施制止了字面的的参数, 利用非凡的参数来挪用存储进程, JDBC驱动能最好以RPC方法直接来执行存储进程。SQL语言上的处理惩罚在这里被制止而且执行也获得很大的改进。

CallableStatement cstmt - conn.prepareCall (
"{call getCustName (?)}");
cstmt.setLong (1,12345);
ResultSet rs = cstmt.executeQuery();

利用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

PreparedStatement ps = conn.prepareStatement(
"INSERT into employees values (?, ?, ?)");
for (n = 0; n < 100; n++) {
ps.setString(name[n]);
ps.setLong(id[n]);
ps.setInt(salary[n]);
ps.executeUpdate();
}

例 2: 利用Batch

PreparedStatement ps = conn.prepareStatement(
"INSERT into employees values (?, ?, ?)");
for (n = 0; n < 100; n++) {
ps.setString(name[n]);
ps.setLong(id[n]);
ps.setInt(salary[n]);
ps.addBatch();
}
ps.executeBatch();

在例 1中, PreparedStatement被用来多次执行INSERT语句。在这里, 执行了100次INSERT操纵, 共有101次网络来回。个中,1次来回是预储statement, 别的100次来回执行每个迭代。在例2中, 当在100次INSERT操纵中利用addBatch()要领时, 只有两次网络来回。1次来回是预储statement, 另一次是执行batch呼吁。固然Batch呼吁会用到更多的数据库的CPU周期, 可是通过淘汰网络来回,机能获得提高。记着, JDBC的机能最大的增进是淘汰JDBC驱动与数据库之间的网络通讯。

#p#副标题#e#

选择符合的光标范例

选择合用的光标范例以最大限度的合用你的应用措施. 本节主要接头三种光标范例的机能问题。

对付从一个表中顺序读取所有记录的环境来说, Forward-Only型的光标提供了最好的机能. 获取表中的数据时, 没有哪种要领比利用Forward-Only型的光标更快. 但不管奈何, 当措施中必需按无序次的方法处理惩罚数据行时, 这种光标就无法利用了。

对付措施中要求与数据库的数据同步以及要可以或许在功效会合前后移动光标, 利用JDBC的Scroll-Insensitive型光标是较抱负的选择。此范例的光标在第一次请求时就获取了所有的数据(当JDBC驱动回收’lazy’方法获取数据时或者是许多的而不是全部的数据)而且储存在客户端。因此, 第一次请求会很是慢, 出格是请求长数据时会理严重。而接下来的请求并不会造成任何网络来回(当利用’lazy’要领时或者只是有限的网络交通) 而且处理惩罚起来很快。因为第一次请求速度很慢, Scroll-Insensitive型光标不该该被利用在单行数据的获取上。当有要返回长数据时, 开拓者也应制止利用Scroll-Insensitive型光标, 因为这样大概会造成内存耗尽。有些Scroll-Insensitive型光标的实现方法是在数据库的姑且表中缓存数据来制止机能问题, 但大都照旧将数据缓存在应用措施中。

#p#分页标题#e#

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语句来取得隐含列的值。

譬喻:

//插入行
int rowcount = stmt.executeUpdate (
"insert into LocalGeniusList (name) values ('Karen')");
// 此刻为新插入的行取得磁盘位置 - rowid
ResultSet rs = stmt.executeQuery (
"select rowid from LocalGeniusList where name = 'Karen'");

这种取得隐含列的方法有两个主要缺点。第一, 取得隐含列是在一个独立的查询中, 它要透过网络送随处事器后再执行。第二, 因为不是主键, 查询条件大概不是表中的独一性ID。在后头一个例子中, 大概返回了多个隐含列的值, 措施无法知道哪个是最后插入的行的值。

(译者:由于差异的数据库支持的水平差异,返回rowid的方法各有差别。在SQL Server中,返回最后插入的记录的id可以用这样的查询语句:SELECT @IDENTITY )

JDBC3.0类型中的一个可选特性提供了一种本领, 可以取得方才插入到表中的记录的自动生成的键值。

譬喻:

int rowcount = stmt.executeUpdate (
"insert into LocalGeniusList (name) values ('Karen')",
// 插入行并返回键值
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys ();
// 获得生成的键值

此刻, 措施中包括了一个独一性ID, 可以用来作为查询条件来快速的存取数据行, 甚至于表中没有主键的环境也可以。

这种取得自动生成的键值的方法给JDBC的开拓者提供了机动性, 而且使存取数据的机能获得晋升.

选择符合的数据范例

#p#分页标题#e#

吸收和发送某些数据大概价钱昂贵. 当你设计一个schema时, 应选择能被最有效地处理惩罚的数据范例. 譬喻, 整型数就比浮点数或实数处理惩罚起来要快一些。浮点数的界说是凭据数据库的内部划定的名目, 凡是是一种压缩名目. 数据必需被 解压和转换到别的种名目, 这样它才气被数据的协议处理惩罚。

获取ResultSet

由于数据库系统对可转动光标的支持有限, 很多JDBC驱动措施并没有实现可转动光标. 除非你确信数据库支持可转动光标的功效集, 不然不要挪用rs.last()和rs.getRow()要领去找出数据集的最大行数。因为JDBC驱动措施模仿了可转动光标, 挪用rs.last()导致了驱动措施透过网络移到了数据集的最后一行。取而代之, 你可以用ResultSet遍历一次计数可能用SELECT查询的COUNT函数来获得数据行数。

凡是环境下,请不要写那种依赖于功效集行数的代码, 因为驱动措施必需获取所有的数据集以便知道查询会返回几多行数据。

 

    关键字:

天才代写-代写联系方式