当您使用Java™开发关系数据库应用程序时,用于JDBC和SQLJ的IBM数据服务器驱动程序不会抛出异常来告诉您Java代码的运行速度是否太慢。 在这种应用程序中,您可能使用IBM Data Server Driver for JDBC和SQLJ将复杂的数据从后端服务器(DB2或Informix)移动到客户端。 如果数据大小很小,这将提供改进的性能。 但是,如果必须将大量数据从服务器传输到客户端,则性能可能会明显下降。 面临的挑战是找到瓶颈并找到解决方案。
本文提供了一些通用准则,这些准则可用于诊断JDBC应用程序的性能问题,从客户端查找瓶颈以及实现提高性能的技术。
这些准则包括:
注:用于JDBC和SQLJ的IBM数据服务器驱动程序(类型4和类型2连接)随DB2 for Linux,UNIX和Windows以及DB2 for zO /S®版本8或更高版本一起提供。 用于JDBC和SQLJ 4型连接的IBM数据服务器驱动程序随IBM Informix一起提供,并且受Informix版本11及更高版本支持。
管理连接
数据库连接是客户端应用程序与数据库服务器软件对话的方式。 创建连接是一项昂贵的事务,因为它可能涉及跨多个尝试连接的客户端的资源分配和管理。 这里有一些简单的技巧,您可以尝试提高驱动程序的性能。
使用DB2 JDBC驱动程序获得更好的性能
- 类型2驱动程序:本地应用程序
当您的JDBC或SQLJ应用程序大部分时间在本地运行时,这些本地应用程序通过2型连接具有更好的性能。 Type 2驱动程序将JDBC API调用转换为特定于数据库的API调用,从而使其最适合那些环境。 使用
DataSource
,很容易实现,如清单1所示:清单1.在本地应用程序中使用类型2驱动程序的示例
javax.sql.DataSource ds = new com.ibm.db2.jcc.DB2SimpleDataSource(); ((com.ibm.db2.jcc.DB2BaseDataSource) ds).setDriverType(2);
- 类型4驱动程序:分布式应用程序
当您的JDBC或SQLJ应用程序大部分时间是远程运行时,这些应用程序通过4类连接具有更好的性能。 Type 4驱动程序是纯Java驱动程序,不需要任何特殊的客户端软件即可转换数据库API调用,因此最适合于分布式环境。 使用
DataSource
实现类型4驱动程序,如清单2所示:清单2.显示如何在分布式应用程序中使用Type 4驱动程序的示例
javax.sql.DataSource ds = new com.ibm.db2.jcc.DB2SimpleDataSource(); ((com.ibm.db2.jcc.DB2BaseDataSource) ds).setDriverType(4);
- 连接池
当多个连接需要同时连接到数据库时,请使用连接池。 连接池利用已缓存和重用的连接。 当应用程序请求与数据库服务器的新连接时,驱动程序将在连接池中搜索缓存的连接。 如果未找到匹配项,则仅创建新连接;否则,将创建新连接。 否则,将重用缓存的连接。 当池化连接关闭时,它不会完全丢失,但会返回到池中以帮助将来重用。
- Connection.close()
每当您退出应用程序时,请使用它。 只要与数据库建立连接,数据库就会为可能在不久将来出现的请求保留资源。 一旦存在应用程序连接,即使没有更多活动在进行,资源也可能不会释放并且可能会保留很长时间。 关闭连接是确保释放这些保留资源的一种方式。 首先关闭语句对象,这将依次关闭所有结果集对象。 然后关闭连接。
清单3.关闭连接的代码
Connection con = ds.getConnection; //perform the actions with this connection con.close(); // before exiting
管理与更新相关的语句
使用程序更新
用于JDBC和SQLJ结果集对象的IBM数据服务器驱动程序为开发人员提供了一种无需执行复杂SQL语句即可更新数据的方法。
注意:结果集需要更新才能使用此技术。
以下是要遵循的步骤:
- 在结果集中提供需要更新的列。
- 提供需要更改的数据。
要在上述情况下更新数据库,您需要在将光标移动到结果集中的下一行之前调用updateRow()
方法。
在下面的代码清单中,使用方法getString()
检索结果集对象“ rs”的Name列的值,并使用方法updateString()
使用字符串值“ Nisha”更新该列。
调用updateRow()
方法以更新数据库中包含更改后的值的行。
清单4.显示程序更新的示例
ResultSet rs;
String name =rs.getString("Name");
rs.updateString("Name","Nisha");
rs.updateRow();
注意:您可以在此处使用列索引获取和更新。
此示例使应用程序更易于维护并提高了性能。 您可能在想,如何?
请考虑以下几点:
- 在执行SELECT语句时,DB2服务器已经位于该行。 因此,我们避免了定位行所需的时间,因为这可能会导致性能下降。
- 如果必须找到该行,则服务器具有一个指向可用行的内部指针(ROWID)。
以上几点仅在您需要检索数据以进行更新时才适用。
使用getBestRowIdentifier()
getBestRowIdentifier()
给出了在WHERE子句中用于更新数据的最佳列集。 尽管这取决于应用程序要求,但这提供了对数据的最快访问。
例如,假设在我们的一个应用程序中,通过调用getPrimaryKeys
或getIndexInfo
来查找唯一的索引列,从而拥有了所有结果列。这将很好地工作,但会导致复杂SQL查询。
考虑以下复杂查询的示例:
清单5.显示数据获取的复杂查询示例
ResultSet rs=rs.executeQuery("Select f_name,l_name,ssn,city,state from employee");
rs.executeUpdate("Update employee set city =? WHERE f_name = ? and l_name = ? and ssn = ?
and city = ? and state = ?");
在开发人员调用getBestRowIdentifier ()
检索伪列(指向记录的确切位置的指针)的情况下,标识特定记录。
DB2支持用户未明确定义的特殊列,但这些特殊列是每个表的隐藏列(例如,ROWID,coltype和模式名称)。 这些列不是我们定义的表的一部分,也不是从getColumns
方法返回的,但是可以更快地访问数据。
考虑清单5中的上一个示例。
清单6.使用getBestRowIdentifier()方法的示例
ResultSet rs = meta.getBestRowIdentifier("DBTEST", "DBCERT", "employee",0,true);
rs.executeUpdate ("Update employee set date = ? where coltype = ?");
选择和使用数据库元数据方法
使用数据库元数据方法可能会降低系统性能,因为它会重复使用结果集对象。 与其他JDBC方法相比,这比较慢。
尽量减少使用数据库元数据方法
应用程序开发人员可以设计其应用程序,以便他们可以缓存由特定数据库的元数据方法生成的结果集返回的信息。 最好的方法是在应用程序中一次调用getTypeInfo
并缓存结果集中与应用程序相关的元素。 任何应用程序都不太可能会使用数据库元数据方法生成的结果集的所有元素,因此应用程序应将列限制为所需的列。 调用不必要的列会降低性能。
避免在数据库元数据方法调用中使用空参数
在数据库元数据方法中使用空参数或搜索模式会导致生成耗时的查询。 始终为生成数据库元数据方法的结果集指定尽可能多的非null参数。 由于不需要的结果,它减少了网络流量。 但是有时我们需要传递一些非null参数,以使函数返回而没有任何错误消息。 由于数据库元数据方法比较慢,因此应用程序应尽可能有效地调用它们。
考虑清单7中的以下示例:
清单7.使用空参数的示例
String[] types = {"TABLE" };
DatabaseMetaData meta = con.getMetaData();
ResultSet rs = meta.getTables(null, null,"Table1", null);
ResultSetMetaData rsmd = rs.getMetaData();
它的编写应如清单8所示。
清单8.使用非空参数的示例
String[] types = {"TABLE" };
DatabaseMetaData meta = con.getMetaData();
ResultSet rs = meta.getTables(Test_Cat, employee,"Table1", types);
ResultSetMetaData rsmd = rs.getMetaData();
在第一个getTables()
调用中,应用程序正在查看表Table1是否存在。 在这里,JDBC驱动程序将请求作为:返回所有表,视图,系统表,同义词,临时表或别名,这些表存在于名为“ Table1”的任何数据库目录内的任何数据库模式中。
对getTables()
的第二次调用更准确地反映了应用程序在寻找什么。 JDBC驱动程序将此请求解释为:返回名称为“ Table1”的当前目录“ Test_Cat”中“雇员”模式中存在的所有表。 显然,JDBC驱动程序可以比处理第一个请求更有效地处理第二个请求。
为了提高性能和可靠性,对于应用程序开发人员来说,设计应用程序的方式很重要,即在使用数据库元数据方法时,它可以在应用程序中提供有关对象的尽可能多的信息。
永远不要调用getColumns
方法来确定表特征
应用程序可以避免使用getColumns()
确定有关表的特征。 而是使用带有getMetadata()
的伪查询。
例如,考虑一个允许用户确定将被选择的列的应用程序。
让我们看看这两种方法中哪种更合适:
- 使用
getColumns
方法。 - 将查询与
getMetadata
方法一起使用。
GetColumns
方法
考虑清单9中所示的示例。它通过生成在服务器端执行的各种查询来生成结果集。
清单9.使用getColumns()方法的示例
ResultSet rs = meta.getColumns(null, null, "TSDCLOB", null);
rs.next();
GetMetadata method
考虑清单10所示的示例。它通过准备一个虚拟查询来生成结果集,该查询已准备好但从未在服务器上执行过。
清单10.使用getMetaData()方法的示例
PreparedStatement ps = con.prepareStatement("SELECT * from TSDCLOB WHERE 1 = 0");
ResultSetMetaData rsmd=ps.getMetaData();
int numcols = rsmd.getColumnCount();
...
int column_type = rsmd.getColumnType(n);//result column information has now been obtained.
...
在这两种情况下,查询都会发送到服务器。 但是在情况1中,必须准备并执行查询,必须生成结果描述信息,并且必须将行的结果集发送给客户端。
在情况2中,必须准备一个简单的查询,并且仅必须生成结果描述信息。 显然,情况2是更好的方法。
总之,请始终使用结果集元数据来检索表列信息,例如列名,列数据类型以及列精度和小数位数。 仅当无法从结果集元数据(例如表列默认值)获得请求的信息时,才使用getColumns()
)。
有效地检索结果集
选择右光标
本节讨论了三种类型的游标的各种性能问题,并提供了为应用程序选择适当类型的游标的准则。
仅前进光标
仅向前游标用于顺序读取表中的所有行。 这是检索结果行的最快方法,但对非顺序读取没有用。
不敏感的光标
在需要DB2服务器上高级别的并发性并且需要在浏览结果集时向前或向后滚动的能力的情况下,请使用不敏感的游标。
处理过程如下:
- 第一个请求获取所有行并将其存储在客户端上。 注意:如果驱动程序正在使用“惰性”获取,则它将获取许多行而不是所有行,并进行存储。 因此,数据请求非常慢,尤其是在数据较长的情况下。
- 所有后续请求都不需要太多处理时间,从而节省了网络流量。
您不应将不敏感的游标用于一行的单个请求。
敏感光标
此类游标用于验证此结果集对更新敏感。 键集和动态光标始终敏感。
某些应用程序避免缓冲不敏感游标的数据。 在这种情况下,敏感游标最适合可滚动游标模型。
滚动浏览整个结果集并检索数据会为顺序行产生大量网络流量。 在这种情况下,性能可能会很慢。 但是,在获取非顺序行的情况下,性能会更好,如清单11所示。
清单11.使用可滚动游标的示例
String str;
String query;
Connection conn;
Statement stmt;
ResultSet rs;
stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
query = "SELECT EMPID FROM EMPLOYEE " +
"FOR UPDATE OF ZIPCODE";
rs = stmt.executeQuery(query);
rs.afterLast();
while (rs.previous()) {
str = rs.getString("EMPID");
System.out.println("Employee ID = " + str);
if (str.compareTo("000101") == 0) {
rs.updateString("ZIPCODE","560043");
rs.updateRow(); }
}
rs.close();
stmt.close();
有效使用get方法
IBM DB2 JDBC提供了多种方法来从结果集中检索数据,例如getInt()
, getString()
和getObject()
。 当指定非默认映射时, getObject()
方法是最通用的,并且性能最差。 这是因为JDBC驱动程序必须进行额外的处理才能确定要检索的值的类型并生成适当的映射。 始终对数据类型使用特定的方法。
始终提供要检索的列的列号。 以下示例显示了使用列名数据的性能提高。
假设在应用程序中使用getString
方法并提供列名作为参数,例如getString("Disha")...
在这里,用于JDBC和SQLJ的IBM DB2驱动程序可能必须将列分隔符“ Disha”转换为大写,然后将“ Disha”与列列表中的所有列名称进行比较。
相反,如果我们使用列索引30,例如getString(30)
,它将直接转到第30列并检索必要的结果,从而节省了大量时间。
我们可以对列索引使用其他几种get方法,如清单12所示:
清单12. Get方法
getString(1),
getLong(2),
and getInt(3), ...so on
检索自动生成的密钥
DB2具有隐藏的列(称为伪列),它们代表表中每一行的唯一键。 通常,在查询中使用这些类型的列是访问行的最快方法,因为伪列通常表示数据的物理磁盘地址。
在下面的代码清单中,应用程序只能在插入数据后立即执行SELECT语句来检索伪列的值。 这发生在JDBC 3.0规范之前。
清单13.使用SELECT语句检索隐藏的列
int rowcount = stmt.executeUpdate ("insert into EmployeeList (name) values ('Disha')");
ResultSet rs = stmt.executeQuery ("select rowid from EmployeeList where name = 'Disha'");
以这种方式检索伪列有两个主要缺点:
- 检索伪列需要通过网络发送一个单独的查询,并在DB2 Connect服务器上执行。
- 如果表上不存在主键,则查询的搜索条件可能无法唯一地标识行。
借助用于JDBC和SQLJ的IBM数据服务器驱动程序,您可以使用JDBC 3.0方法来检索在执行INSERT语句以将行插入表中时自动生成的键。
在下面的示例中,可以返回多个伪列值,并且应用程序可能无法确定哪个值实际上是最近插入的行的值。
清单14.检索自动生成的键
int rowcount = stmt.executeUpdate ("insert into EmployeeList (name) values ('Disha')",
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys ();
现在,该应用程序包含一个可在搜索条件下使用的值,以提供对行的最快访问,以及一个值,该值即使在表上不存在主键时也可以唯一地标识该行。
检索自动生成的键的功能为DB2 JDBC开发人员提供了灵活性,并在访问数据时提高了性能。
避免移动当时不需要的额外数据
本部分提供了详细的准则,用于在使用IBM Data Server Driver for JDBC和SQLJ应用程序有效地检索数据时优化系统性能。
选择正确的数据类型
DB2 JDBC应用程序开发人员应该设计一个模式,并选择可以由数据库协议更快处理的数据类型。 有时检索和发送某些数据类型可能会很昂贵。
通常,浮点数据是根据内部特定于数据库的格式定义的,大多数为压缩格式。 因此,无论何时不需要浮点数据,都应使用整数数据,因为它比浮点数据或Bigdecimal数据的处理速度更快。
减少检索数据的大小
为了减少网络流量并提高性能,用于JDBC和SQLJ的IBM数据服务器驱动程序为您提供了几种方法,这些方法可以通过调用setMaxRows()
, setMaxFieldSize()
和setFetchSize()
将检索到的任何数据的大小减小到一定setFetchSize()
。 您还可以通过减小列的大小来减小要检索的数据的大小。
仅返回所需的行和列。 如果在只需要五列的情况下返回十列,则会降低性能-特别是在不必要的行包含长数据的情况下。
LOB定位器支持
在数据库不支持渐进式流传输但仍必须检索LOB数据的情况下, fullyMaterializeLobData
调用非常有用。 在这种情况下,用于JDBC和SQLJ的IBM数据驱动程序在内部使用定位器按需检索成块的LOB数据。 强烈建议您在检索包含大量数据的LOB时将此值设置为“ false”。
在数据库服务器支持渐进式流传输的情况下,无论是否设置了progressiveStreaming
属性,JDBC驱动程序都会忽略fullyMaterializeLobData
的值。
注意:您不能使用单个LOB定位器在两个不同的数据库之间移动数据。 要在两个数据库之间移动LOB数据,需要在从第一个数据库中的表中检索该LOB数据时将其具体化,然后将该数据插入第二个数据库中的表中。
以正确的方式使用正确类型的语句对象
使用正确类型的语句对象执行操作与为汽车选择正确的轮胎一样重要。 知道在什么时间使用哪种类型的语句与您要执行的操作是一对一的简单类比。 语句,PreparedStatments,CallableStaments和ResultSet对象是大多数JDBC应用程序的组件。
让我们看看何时使用什么语句对象。
语句对象
由于当SQL请求发送或以任何方式对数据库进行更改时,这就像对数据库SQL请求的载体一样,因此请使用stmt.executeUpdate()
方法。 例如,此方法应用于INSERT,UPDATE或DELETE。 当发送SQL请求是查询时,请使用stmt.executeQuery()方法。 这将返回保存查询结果的ResultSet对象。
PreparedStatement对象
当您有一条要重复发送到服务器SQL语句时, PreparedStatement
是最佳选择。 创建PreparedStatement
时, PreparedStatement
SQL语句的某些部分发送到服务器,为该SQL生成访问计划,并将SQL缓存。 如果将来出现类似的查询请求,则不会从头开始准备SQL,而是改用缓存的Prepared语句。
例如,为了将100个值插入表TAB1中,而不是运行100个实例stmt.executeUpdate ("INSERT INTO TAB1 values (10)");
在一个循环中,将创建一个准备好的语句,如下所示:
清单15. PreparedStatement示例
pstmt= con.createPreparedStatement(“INSERT INTO TAB1 VALUES (?)”);
for(int j ; j<100; j++)
{pstmt.setInt(1,j);
pstmt.addBatch();
pstmt.executeBatch();}
上面的代码可能会提高性能,因为SQL在插入100个值时仅准备一次。
考虑下面的语句对象代码:
清单16.语句对象
String str = ("insert into t1 values (100)");
s.executeUpdate(str);
到服务器的一次网络访问。
清单17.对于prepareStatement对象
java.sql.PreparedStatement ps = con.prepareStatement ("insert into t1 values (?)");
for(int j=0;j<100;j++)
{ ps.setInt(1, j);
ps.addBatch();}
ps.executeBatch();
这表明在一次网络旅行中, PreparedStatement
已将100个值插入到表中,而语句对象仅插入了一个。
CallableStatement对象
可调用的语句用于访问存储过程: CallableStatement CStmt= Connection.prepareCall("{call storedProc1(?, ?, ?)}");
。 明智地使用牙套。 花括号可以在任何数据库的存储过程中使用,但是如果不需要它们,即如果您确定正在使用的DBMS的要求并且不需要使用转义类型语法,那么请避免使用这些花括号因为它们消耗了额外的资源。
结果集
ResultSet
用于保存SQL查询返回的数据。 使用更智能的结果集,您只能获取所需的数据。 考虑到性能,在整个长度和广度上处理完整的结果集将无用。 因此,要拥有更智能的结果集,就需要更智能的语句对象。
下面的示例使您可以灵活地滚动结果集并动态访问所需的记录,这与默认行为相反,在默认行为中,您只能向前浏览结果集,并且一旦耗尽它就无法继续读取结果。
清单18. ResultSet示例
Statement st = conn.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
这是一些在ResultSet周围动态移动光标的简单方法。 (提高性能的最佳方法是使用正确的WHERE子句来构造查询,以便仅检索所需的数据。在查询中使用ORDER BY子句时,动态定位的游标将导致获取不同的值。 ,因为行顺序往往会发生变化。)
清单19.提取了30行的ResultSet,直接转到第25行并进行更新
rs.absolute(25); // cursor is moved to the 25th row
rs.updateString("Col1", "BANANA"); // updates the 25th column
rs.updateRow(); // updates the row
清单20.提取了30行的ResultSet,以获取第25至28行
rs.absolute(25);
rs.relative(3); // cursor will move 3 rows ahead
清单21.提取了30行的ResultSet,要获取第22到第25行
rs.absolute(25);
rs.relative(-3); // cursor will move 3 rows back
清单22.提取了30行的ResultSet,以获取最后5行,以防万一您不知道返回多少行
int i =-1;
while (rs.absolute(i++)){..};
// the last row is represented as -1, the 2nd last as -2 and so on.
有效处理数据
用于JDBC和SQLJ的IBM数据服务器驱动程序提供了快速的选项,可以将数据发送到DB2服务器,如以下各节所述。
使用streamBufferSize
传输流以获取LOB和XML数据
现实世界中使用的XML数据可能非常庞大,并且在将其发送到数据库之前可能需要大量内存来保存它。 这会导致高昂的内存成本并影响性能。 您可以通过设置流缓冲区大小来指定DB2 JDBC驱动程序将缓冲用于分块LOB或XML数据的大小(以字节为单位)来使用渐进式流传输。
JDBC驱动程序使用streamBufferSize
值,而与渐进式流无关。 可以如清单23所示进行设置:
清单23.显示设置streamBufferSize值的示例
((com.ibm.db2.jcc.DB2BaseDataSource) ds).setStreamBufferSize(10000000);
//the buffer is set to 10000000 bytes.
因此,如果要发送的数据大于10000000,则不会实现数据而是将其传输。 如果数据大小小于10000000,则在将其发送回客户端之前将其实现。
使用deferedPrepares
和sendDataAsIs
deferPrepares
将准备请求延迟到执行时间。 这样可以节省网络行程,因为准备和执行都在一次行程中进行。
sendDataAsIs
设置为false时,将猜测数据类型。 默认情况下禁用。 为达到最佳性能,设置deferPrepares
为true, sendDataAsIs
为true。
为了检查网络行程和其他系统监视数据的数量,DB2 JDBC驱动程序提供了一个类com.ibm.db2.jcc.DB2SystemMonitor
,如清单24所示:
清单24.示例要检查网络旅行次数和系统监视数据
((com.ibm.db2.jcc.DB2BaseDataSource) ds).setDeferPrepares ((true));
//prepare operation would be deferred
((com.ibm.db2.jcc.DB2BaseDataSource) ds).sendDataAsIs=true;
....
...
//to monitor the performance after this is set this could be tried
DB2SystemMonitor systemMonitor =((DB2Connection)conn).getDB2SystemMonitor();
// this returns the system monitor object
systemMonitor.enable(true);
systemMonitor.start(DB2SystemMonitor.RESET_TIMES);
java.sql.PreparedStatement ps = conn.prepareStatement ("insert into t1 values (?)");
......... //iterating through loop for 100 values
{
ps.setInt(1, j);
ps.addBatch();
}
ps.executeBatch();
systemMonitor.stop();
System.out.println("NUMBER of the NETWORK_TRIPS ="
+ systemMonitor.moreData(NUMBER_NETWORK_TRIPS));
结论
在编写JDBC和SQLJ应用程序时,可以使用许多方法来帮助调整DB2以获得最佳性能。 本文介绍了一些易于实现的编程方法,您可以使用这些方法来增强JDBC驱动程序的性能。 遵循我们描述的简单技术,在构建更智能,更健壮的高性能数据库应用程序方面获得良好的开端。
翻译自: https://www.ibm.com/developerworks/data/library/techarticle/dm-1211jdbcapplication/index.html