通常,当 Microsoft JDBC Driver for SQL Server 执行查询时,驱动程序会将服务器中的所有结果检索到应用程序内存中。尽管这种方法可以最大程度地减少 SQL Server 上的资源占用,但它可能会在 JDBC 应用程序中针对生成非常大的结果的查询引发 OutOfMemory(OOM)错误。查询二进制对象或者海量数据(本文桐城大数据)的结果集时,查询的响应时间更长,更容易出现OOM的问题,SQL Server作为成熟的数据库,有自己的解决方式,本文主要讨论java中的实现:
一、通过数据库游标的方式,控制结果集的每次返回的结果大小,来降低大数据查询的资源消耗和响应时间:
1.使用SQL Server JDBC的URL为selectMethod=cursor类型(默认selectMethod=direct):
String url="jdbc:sqlserver://127.0.0.1;databaseName=lyy;selectMethod=cursor";
这种方式会对所有使用该url的连接下的所有查询起作用,可能引起其他普通情况的读取性能下降。
2.设置Fetch Size,要想该设置起效必须使用SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY类型的Statement:
Statement stmt = con.createStatement(SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(100);
这种方式会造成SQL Server的API侵入,但该设置仅对使用该statement当前查询起作用,不失为一种更好的办法。
参考官方文档:http://technet.microsoft.com/en-us/library/aa342344%28SQL.90%29.aspx
二、使用自适应缓冲(Adaptive Buffering),每次仅占用必要的资源,来降低大数据查询的资源消耗和响应时间。
1.设置url为responseBuffering=adaptive类型,不同版本驱动的默认responseBuffering有所不同,详情参考:http://technet.microsoft.com/en-us/library/ms378988(v=sql.100).aspx
String url = "jdbc:sqlserver://192.168.100.136:1433;DatabaseName = lyy;responseBuffering=adaptive";
这种方式会对所有使用该url的连接下的所有查询起作用,可能引起其他普通情况的读取性能下降。
2. 通过 SQLServerStatement 对象的setResponseBuffering方法来设置response buffering
code1:
stmt = conn.createStatement();
//Display the response buffering mode.
SQLServerStatement SQLstmt = (SQLServerStatement) stmt;
SQLstmt.setResponseBuffering("adaptive");
System.out.println("Response buffering mode is: " + SQLstmt.getResponseBuffering());
rs = stmt.executeQuery(sql);
code:2:
stmt = conn.createStatement();
if (stmt.isWrapperFor(com.microsoft.sqlserver.jdbc.SQLServerStatement.class))
{
SQLServerStatement SQLstmt = stmt.unwrap(SQLServerStatement.class);
SQLstmt.setResponseBuffering("adaptive");
System.out.println("Response buffering mode has been set to " + SQLstmt.getResponseBuffering());
}
rs = stmt.executeQuery(sql);
开发人员应遵循以下重要准则,以尽可能减少应用程序占用的内存:
应避免使用连接字符串属性 selectMethod=cursor 来允许应用程序处理非常大的结果集。自适应缓冲功能允许应用程序在不使用服务器游标的情况下处理非常大的只进、只读结果集。请注意,设置 selectMethod=cursor 时,该连接生成的所有只进只读结果集都会受到影响。换言之,如果应用程序例行处理只有几行的短结果集,则与没有将 selectMethod 设置为 cursor 的情况相比,针对每个结果集创建、读取和关闭服务器游标在客户端和服务器端都会使用更多的资源。
通过使用 getAsciiStream、getBinaryStream, 或 getCharacterStream 方法(而不是 getBlob 或 getClob 方法),将大文本或二进制值作为流进行读取。从版本 1.2 开始,SQLServerCallableStatement 类提供了新的 get<Type>Stream 方法来实现此目的。
确保在 SELECT 语句中将可能具有大值的列放在列列表的最后,并且使用 SQLServerResultSet 的 get<Type>Stream 方法按选择列时的顺序来访问这些列。
确保在用来创建 SQLServerCallableStatement 的 SQL 语句的参数列表中,最后声明可能具有大值的 OUT 参数。此外,确保使用 SQLServerCallableStatement 的get<Type>Stream 方法,按照声明 OUT 参数时的顺序访问这些参数。
避免同时对同一连接执行一条以上的语句。如果在处理上一条语句的结果之前执行另一条语句,可能导致将未处理的结果缓冲到应用程序内存中。
但在某些情况下,使用 selectMethod=cursor 而不是 responseBuffering=adaptive 可能更有利,例如:
在这两种情况下,需要考虑创建、读取和关闭服务器游标的开销。
如果应用程序对只进只读结果集的处理速度很慢(例如,在某些用户输入后再读取每一行),则使用 selectMethod=cursor 代替 responseBuffering=adaptive可以有助于减少 SQL Server 使用的资源。
如果应用程序在同一连接上同时处理两个或更多的只进只读结果集,则处理这些结果集时,使用 selectMethod=cursor 代替 responseBuffering=adaptive 可能有助于减少驱动程序需要的内存。
此外,下面的列表针对可滚动的结果集和只进的可更新结果集提供了一些建议:
对于可滚动结果集,在提取行块时,驱动程序始终会将 SQLServerResultSet 对象的 getFetchSize 方法所指示的行数读入内存,即使在启用了自适应缓冲的情况下也是如此。如果滚动导致 OutOfMemoryError,您可以调用 SQLServerResultSet 对象的 setFetchSize 方法将提取大小设置为较少的行数(如果必要,甚至可减小到 1 行),从而减少提取的行数。如果这样还是无法防止 OutOfMemoryError, ,则应避免在可滚动的结果集中包含非常大的列。
对于只进的可更新结果集,在提取行块时,驱动程序通常会将 SQLServerResultSet 对象的 getFetchSize 方法所指示的行数读入内存,即使在该连接上启用了自适应缓冲的情况下也是如此。如果调用 SQLServerResultSet 对象的 next 方法导致 OutOfMemoryError,您可以调用 SQLServerResultSet 对象的 setFetchSize 方法将提取大小设置为较少的行数(如果必要,甚至可减小到 1 行),从而减少提取的行数。执行该语句前,也可以调用 SQLServerStatement 对象的 setResponseBuffering 方法并提供参数“adaptive”来强制驱动程序不缓冲任何行。因为该结果集是不可滚动的,所以如果应用程序使用 get<Type>Stream 方法之一访问大型列值,驱动程序将会在应用程序读取该值后立即将其丢弃,正如对只进只读结果集所做的那样。
3.通过调用 SQLServerDataSource 对象的 setResponseBuffering 方法,为通过该 SQLServerDataSource 对象创建的connection来设置 response buffering模式。
该方式通常是用于java web应用。
参考官方文档:http://technet.microsoft.com/en-us/library/bb879937(v=sql.110).aspx
三、考虑使用 setMaxRows 方法(或 SET ROWCOUNT 或 SELECT TOP N SQL 语法)来限制从可能较大的结果集中返回的行数,对于不需要的结果尽量不去查询。
可以使用setMaxRows的方法来查询表的指定前几行,而不是把整个表的数据行全部都查询出来。Statement/PreparedStatement setMaxRows(N)等价于SELECT TOP N SQL 语法,其中:
N<=表的总行数时,返回的结果集为表的前N行。
N>表的总行数时,返回的结果集为表的总行数,不报错。
String sql ="select id,name,data from dbo.blobtable ";
stmt = conn.prepareStatement(sql);
stmt.setMaxRows(2);
rs = stmt.executeQuery();
//或者
String sql ="select id,name,data from dbo.blobtable ";
stmt = conn.createStatement();
stmt.setMaxRows(2);
rs = stmt.executeQuery(sql);
//等价于:
String sql ="select top 2 id,name,data from dbo.blobtable ";
stmt = conn.prepareStatement(sql);
rs = stmt.executeQuery();
//或者
String sql ="select top 2 id,name,data from dbo.blobtable ";
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
这种方式并不适用于所有情况,需要酌情选择。
参考官方文档: http://technet.microsoft.com/en-us/library/aa342344%28SQL.90%29.aspx
四、测试并比较一、二两种方式
数据:sqlserver2008,一个表(2600行,每行数据3.3M左右)
操作:查询所欲哦数据,并遍历每行结果。
测试结果:
方法 | 使用的时间 | 内存使用情况 | 垃圾回收情况 |
1.使用adaptive buffering | 4分28秒 | <=80m,内存居高恒定 | 垃圾回收占用的时间约0.2% |
2.使用游标fetch size=10 | 5分13秒 | <=80m,内存交替高低起伏 |
垃圾回收占用的时间约2% |
以上结果可见,方法1更快速,但是垃圾回收较差;方法2速度满了一些,但是垃圾回收好一些;对于已知表数据详情的,可以通过方法2设置合适的fetch size来优化查询的资源消耗和响应时间;对于不知道表数据详情的可以使用方法1来优化。
来源:oschina
链接:https://my.oschina.net/u/1158288/blog/331668