JDBC batchsize 和 fetchsize的理解

分类: DB |
Batch相当于JDBC的写缓冲,Fetch相当于读缓冲。
-
import java.sql.Connection;
-
import java.sql.DriverManager;
-
import java.sql.PreparedStatement;
-
import java.sql.ResultSet;
-
import java.sql.SQLException;
-
import java.util.ArrayList;
-
import java.util.List;
-
-
public
class Test {
-
private static int _1W = 10000;
-
private static List<</span>String> list = new ArrayList<</span>String>(100 * _1W); -
static {
-
for (int i = 0; i <</span> 10 * _1W; i++) {
-
list.add(String.valueOf(i));
-
}
-
}
-
-
public static void main(String[] args) throws ClassNotFoundException, SQLException {
-
long start = System.currentTimeMillis();
-
fetchRead();
-
long end = System.currentTimeMillis();
-
System.out.println((end - start) + "ms");
-
}
-
-
public static void batchWrite() throws SQLException, ClassNotFoundException {
-
// 1108ms
-
Class.forName("oracle.jdbc.OracleDriver");
-
Connection connection = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "xxx", "xxx");
-
connection.setAutoCommit(false);
-
PreparedStatement cmd = connection.prepareStatement("insert into t values(?)"); -
for (int i = 0; i <</span> list.size(); i++) {
-
cmd.setString(1, list.get(i));
-
cmd.addBatch();
-
if (i % _1W == 0) {
-
cmd.executeBatch();
-
}
-
}
-
cmd.executeBatch();
-
connection.commit();
-
}
-
-
public static void jdbcWrite() throws ClassNotFoundException, SQLException {
-
// 28189ms
-
Class.forName("oracle.jdbc.OracleDriver");
-
Connection connection = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "xxx", "xxx");
-
connection.setAutoCommit(false);
-
PreparedStatement cmd = connection.prepareStatement("insert into t values(?)"); -
for (String s : list) {
-
cmd.setString(1, s);
-
cmd.execute();
-
}
-
connection.commit();
-
}
-
-
public static void jdbcRead() throws ClassNotFoundException, SQLException {
-
// 3120ms
-
Class.forName("oracle.jdbc.OracleDriver");
-
Connection connection = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "xxx", "xxx");
-
connection.setAutoCommit(false);
-
PreparedStatement cmd = connection.prepareStatement("select * from t"); -
ResultSet rs = cmd.executeQuery(); -
int i = 0;
-
while (rs.next()) {
-
rs.getString(1);
-
i = i + 1;
-
}
-
System.out.println("count:" + i);
-
}
-
-
public static void fetchRead() throws ClassNotFoundException, SQLException {
-
//764ms
-
Class.forName("oracle.jdbc.OracleDriver");
-
Connection connection = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "xxx", "xxx");
-
connection.setAutoCommit(false);
-
PreparedStatement cmd = connection.prepareStatement("select * from t"); -
cmd.setFetchSize(_1W);
-
ResultSet rs = cmd.executeQuery(); -
int i = 0;
-
while (rs.next()) {
-
rs.getString(1);
-
i = i + 1;
-
}
-
System.out.println("count:" + i);
-
}
- }
JDBC读取比较,相差4倍
如果把JDBC类比为JAVA IO的话,
不使用Fetch和Batch相当于直接使用FileInputStream和FileOutputStream
而设置了Fetch和Batch相当于使用BufferedInputStream和BufferedOutputStream
fetch Size 是设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数。例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会1次性把1万条取出来的,而只会取出Fetch Size条数,当纪录集遍历完了这些记录以后,再去数据库取Fetch Size条数据。因此大大节省了无谓的内存消耗。当然Fetch Size设的越大,读数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢。这有点像平时我们写程序写硬盘文件一样,设立一个Buffer,每次写入Buffer,等Buffer满了以后,一次写入硬盘,道理相同。
总结下这周帮助客户解决报表生成操作的mysql
驱动的使用上的一些问题,与解决方案。由于生成报表逻辑要从数据库读取大量数据并在内存中加工处理后在
生成大量的汇总数据然后写入到数据库。基本流程是 读取->处理->写入。
1 读取操作开始遇到的问题是当sql查询数据量比较大时候基本读不出来。开始以为是server端处理太慢。但是在控制台是可以立即返回数据的。于是在应用
这边抓包,发现也是发送sql后立即有数据返回。但是执行ResultSet的next方法确实阻塞的。查文档翻代码原来mysql驱动默认的行为是需要把整个结果全部读取到
内存中才开始允许应用读取结果。显然与期望的行为不一致,期望的行为是流的方式读取,当结果从myql服务端返回后立即还是读取处理。这样应用就不需要大量内存
来存储这个结果集。正确的流式读取方式代码示例:
PreparedStatement ps = connection.prepareStatement("select .. from ..",
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
//forward only read only也是mysql 驱动的默认值,所以不指定也是可以的 比如: PreparedStatement ps = connection.prepareStatement("select .. from ..");
ps.setFetchSize(Integer.MIN_VALUE); //也可以修改jdbc url通过defaultFetchSize参数来设置,这样默认所以的返回结果都是通过流方式读取.
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("fieldName"));
}
代码分析:下面是mysql判断是否开启流式读取结果的方法,有三个条件forward-only,read-only,fatch
size是Integer.MIN_VALUE
protected boolean createStreamingResultSet() {
try {
synchronized(checkClosed().getConnectionMutex()) {
return ((this.resultSetType ==
&& (this.resultSetConcurrency == ;
}
} catch (SQLException e) {
// we can't break the interface, having this be no-op in case of error is ok
return false;
}
}