加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

JDBC batchsize 和 fetchsize的理解

(2017-02-28 22:33:15)
分类: DB
Batch和Fetch两个特性非常重要。
Batch相当于JDBC的写缓冲,Fetch相当于读缓冲。

  1. import java.sql.Connection;
  2. import java.sql.DriverManager;
  3. import java.sql.PreparedStatement;
  4. import java.sql.ResultSet;
  5. import java.sql.SQLException;
  6. import java.util.ArrayList;
  7. import java.util.List;

  8. public class Test {
  9.     private static int _1W = 10000;
  10.     private static List<</span>String> list = new ArrayList<</span>String>(100 * _1W);
  11.     static {
  12.         for (int = 0; <</span> 10 * _1W; i++) {
  13.             list.add(String.valueOf(i));
  14.         }
  15.     }

  16.     public static void main(String[] args) throws ClassNotFoundException, SQLException {
  17.         long start = System.currentTimeMillis();
  18.         fetchRead();
  19.         long end = System.currentTimeMillis();
  20.         System.out.println((end - start) + "ms");
  21.     }

  22.     public static void batchWrite() throws SQLException, ClassNotFoundException {
  23.         // 1108ms
  24.         Class.forName("oracle.jdbc.OracleDriver");
  25.         Connection connection = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "xxx", "xxx");
  26.         connection.setAutoCommit(false);
  27.         PreparedStatement cmd = connection.prepareStatement("insert into t values(?)");
  28.         for (int = 0; <</span> list.size(); i++) {
  29.             cmd.setString(1, list.get(i));
  30.             cmd.addBatch();
  31.             if (% _1W == 0) {
  32.                 cmd.executeBatch();
  33.             }
  34.         }
  35.         cmd.executeBatch();
  36.         connection.commit();
  37.     }

  38.     public static void jdbcWrite() throws ClassNotFoundException, SQLException {
  39.         // 28189ms
  40.         Class.forName("oracle.jdbc.OracleDriver");
  41.         Connection connection = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "xxx", "xxx");
  42.         connection.setAutoCommit(false);
  43.         PreparedStatement cmd = connection.prepareStatement("insert into t values(?)");
  44.         for (String : list) {
  45.             cmd.setString(1, s);
  46.             cmd.execute();
  47.         }
  48.         connection.commit();
  49.     }

  50.     public static void jdbcRead() throws ClassNotFoundException, SQLException {
  51.         // 3120ms
  52.         Class.forName("oracle.jdbc.OracleDriver");
  53.         Connection connection = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "xxx", "xxx");
  54.         connection.setAutoCommit(false);
  55.         PreparedStatement cmd = connection.prepareStatement("select * from t");
  56.         ResultSet rs = cmd.executeQuery();
  57.         int = 0;
  58.         while (rs.next()) {
  59.             rs.getString(1);
  60.             = + 1;
  61.         }
  62.         System.out.println("count:" + i);
  63.     }

  64.     public static void fetchRead() throws ClassNotFoundException, SQLException {
  65.         //764ms
  66.         Class.forName("oracle.jdbc.OracleDriver");
  67.         Connection connection = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "xxx", "xxx");
  68.         connection.setAutoCommit(false);
  69.         PreparedStatement cmd = connection.prepareStatement("select * from t");
  70.         cmd.setFetchSize(_1W);
  71.         ResultSet rs = cmd.executeQuery();
  72.         int = 0;
  73.         while (rs.next()) {
  74.             rs.getString(1);
  75.             = + 1;
  76.         }
  77.         System.out.println("count:" + i);
  78.     }
  79. }
JDBC插入比较,相差25倍
    不带Batch    28189ms
    Batch           1108ms 

JDBC读取比较,相差4倍
    不带Fetch    3120ms
    Fetch           764ms

如果把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满了以后,一次写入硬盘,道理相同。

 

     Oracle数据库的JDBC驱动默认的Fetch Size=10,是一个非常保守的设定,根据测试,当Fetch Size=50的时候,性能会提升1倍之多,当Fetch Size=100,性能还能继续提升20%,Fetch Size继续增大,性能提升的就不显著了,反而会消耗过多的内存 因此建议使用Oracle时至少要将Fetch Size设到50 

 

     不过并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持 。MySQL就像上面说的那种最坏的情况,他总是一下就把1万条记录完全取出来,内存消耗会非常非常惊人!这个情况就没有什么好办法了。

 

     Batch Size是设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,有点相当于设置Buffer缓冲区大小的意思。Batch Size越大,批量操作的向数据库发送sql的次数越少,速度就越快。一个测试结果是当Batch Size=0的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,Batch Size = 50的时候,删除仅仅需要5秒!!!   

 

     另外hibernate.max_fetch_depth 设置外连接抓取树的最大深度取值. 建议设置为0到3之间。就是每次你在查询时,会级联查询的深度,譬如你对关联vo设置了eager的话,如果fetch_depth值太小的话,会发多很多条sql,影响查询速率。



总结下这周帮助客户解决报表生成操作的mysql 驱动的使用上的一些问题,与解决方案。由于生成报表逻辑要从数据库读取大量数据并在内存中加工处理后在

生成大量的汇总数据然后写入到数据库。基本流程是 读取->处理->写入。

1 读取操作开始遇到的问题是当sql查询数据量比较大时候基本读不出来。开始以为是server端处理太慢。但是在控制台是可以立即返回数据的。于是在应用

这边抓包,发现也是发送sql后立即有数据返回。但是执行ResultSet的next方法确实阻塞的。查文档翻代码原来mysql驱动默认的行为是需要把整个结果全部读取到

内存中才开始允许应用读取结果。显然与期望的行为不一致,期望的行为是流的方式读取,当结果从myql服务端返回后立即还是读取处理。这样应用就不需要大量内存

来存储这个结果集。正确的流式读取方式代码示例:

 

http://common.cnblogs.com/images/copycode.gifbatchsize 和 fetchsize的理解" />

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"));
}
http://common.cnblogs.com/images/copycode.gifbatchsize 和 fetchsize的理解" />

 


代码分析:下面是mysql判断是否开启流式读取结果的方法,有三个条件forward-only,read-only,fatch size是Integer.MIN_VALUE

http://common.cnblogs.com/images/copycode.gifbatchsize 和 fetchsize的理解" />

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;
    }
}							
		

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有