提高 JDBC 性能

Posted

技术标签:

【中文标题】提高 JDBC 性能【英文标题】:Improve JDBC Performance 【发布时间】:2014-09-15 14:37:19 【问题描述】:

我正在我的 java 应用程序中执行以下一组语句。它连接到一个 oracle 数据库。

stat=connection.createStatement();
stat1=commection.createstatement();
ResultSet rs = stat.executeQuery(BIGQUERY);
while(rs.next()) 
    obj1.setAttr1(rs.getString(1));
    obj1.setAttr2(rs.getString(1));
    obj1.setAttr3(rs.getString(1));
    obj1.setAttr4(rs.getString(1));

    ResultSet rs1 = stat1.executeQuery(SMALLQ1);
    while(rs1.next()) 
       obj1.setAttr5(rs1.getString(1));
    

     ResultSet rs2 = stat1.executeQuery(SMALLQ2);
    while(rs2.next()) 
       obj1.setAttr6(rs2.getString(1));

    
         .
         .
         .
 LinkedBlockingqueue.add(obj1);
 
 //all staements and connections close

BIGQUERY 返回大约 450 万条记录,对于每条记录,我必须执行较小的查询,数量为 14。每个小查询都有 3 个内部连接语句。

我的多线程应用程序现在可以在一小时内处理 90,000 个。但我可能必须每天运行代码,所以我想在 20 小时内处理所有记录。我正在使用大约 200 个线程来处理上述代码并将记录存储在链接阻塞队列中。

盲目地增加线程数有助于提高性能还是有其他方法可以提高结果集的性能?

PS:我无法在此处发布查询,但我确信所有查询都已优化。

【问题讨论】:

为什么要在 Java 中实现连接?数据库将更有效地做到这一点。 对不起,应该已经很清楚了。 BIGQUERY 是一个有连接的 sql 语句,我只是在执行它。 我不是在谈论 bigquery 中的联接。您的代码实现了 bigquery 和 smallq1 之间的嵌套循环连接以及 bigquery 和 smallq2 之间的另一个嵌套循环连接。与您使用 Java/JDBC 创建的任何解决方案相比,数据库可以做的事情很多 (Oracle 也可以为您做多线程)。为循环的每次迭代在循环内运行 sql 语句应该会敲响 很多 个警钟。 我假设您的 BIGQRY 和 SMALLQRYx 具有不容易实现纯 sql 的功能和依赖关系,这就是您进行客户端杂耍的原因。您可以将多个 SMALLQRYx 批处理到同一个查询语句吗? JdbcDriver+DB 可能不支持返回多个结果集,但是如果 smalls 或多或少相似的选择查询,请使用“SELECT 1 as smallidx, col1, col2..UNION ALL..SELECT 2 as smallidx, col1, col2...”? 尝试 UNIONALL_BATCH 技巧,如果有所作为,extracol 告诉客户端循环该行所属的位置。在 t1.attr1=t2.attr1 和 t2.attr2='VALUE1' 内连接 table3 t3 on t1.attr1=t3.attr1 和 t1.attr1=t3.attr2= 'VALUE2' and t1.attr3='VALUE3' UNION ALL select 2 as smallidx, attr1, attr2, attr3 from table1 t1 inner join table2 t2 on t1.attr1=t2.attr1 and t2.attr2='VALUE4' inner join table3 t3在 t1.attr1=t3.attr1 和 t3.attr2='VALUE5' 和 t1.attr3='VALUE6' UNION ALL ...VALUE7+VALUE8+VALUE9 【参考方案1】:

要为您的方案提高 JDBC 性能,您可以应用一些修改。

如您所见,所有这些修改都可以显着加快您的任务。

1.使用批处理操作。

您可以读取大型查询并将结果存储在某种缓冲区中。 只有当缓冲区已满时,您才应该对缓冲区中收集的所有数据运行子查询。 这大大减少了要执行的 SQL 语句的数量。

static final int BATCH_SIZE = 1000; 
List<MyData> buffer = new ArrayList<>(BATCH_SIZE);

while (rs.hasNext()) 

  MyData record = new MyData( rs.getString(1), ..., rs.getString(4) );
  buffer.add( record );

  if (buffer.size() == BATCH_SIZE) 
    processBatch( buffer );
    


void processBatch( List<MyData> buffer ) 

  String sql = "select ... where X and id in (" + getIDs(buffer) + ")";
  stat1.executeQuery(sql);  // query for all IDs in buffer
  while(stat1.hasNext())  ... 
  ...  

2。使用高效的地图来存储来自多个选择的内容。

如果您的记录没有那么大,您可以一次将它们全部存储在 400 万张表中。

我在夜间进程中多次使用这种方法(没有普通用户)。 这种方法可能需要大量堆内存(即 100 MB - 1 GB) - 但比方法 1) 快得多。

为此,您需要高效的地图实现,即 - gnu.trove.map.TIntObjectMap (etc) 这比java标准库映射要好得多。

final TIntObjectMap<MyData> map = new TIntObjectHashMap<MyData>(10000, 0.8f);

// query 1
while (rs.hasNext()) 
  MyData record = new MyData( rs.getInt(1), rs.getString(2), ..., rs.getString(4) );
  map.put(record.getId(), record);


// query 2
while (rs.hasNext()) 
  int id = rs.getInt(1);   // my data id
  String x = rs.getString(...);
  int y = rs.getInt(...);

  MyData record = map.get(id);
  record.add( new MyDetail(x,y) );


// query 3
// same pattern as query 2 

在此之后,您将在地图上填满收集到的所有数据。可能分配了很多内存。 这就是为什么只有在拥有此类资源时才能使用该方法的原因。

另一个主题是如何将 MyData 和 MyDetail 类编写得尽可能小。 您可以使用一些技巧:

    在 1 个长变量中存储 3 个整数(范围有限)(使用 util 进行位移) 将 Date 对象存储为整数 (yymmdd) 为从数据库中获取的每个字符串调用 str.intern()

3。交易

如果您必须进行一些更新或插入,超过 400 万条记录就无法处理事务。 这对于大多数数据库配置来说太过分了。 使用方法 1) 并为每个批次提交事务。 在每条新插入的记录上,您都可以有类似 RUN_ID 的内容,如果一切顺利,您可以将此 RUN_ID 标记为成功。

如果您的查询只读取 - 没有问题。但是,您可以将事务标记为只读以帮助您的数据库。

4. Jdbc 获取大小。

当您从数据库加载大量记录时,在您的 jdbc 连接上设置适当的提取大小非常非常重要。 这会减少对数据库套接字的物理命中次数并加快您的进程。

例子:

// jdbc
statement.setFetchSize(500);

// spring     
JdbcTemplate jdbc = new JdbcTemplate(datasource);
jdbc.setFetchSize(500);

您可以在这里找到一些基准和使用获取大小的模式:

http://makejavafaster.blogspot.com/2015/06/jdbc-fetch-size-performance.html

5. PreparedStatement

使用 PreparedStatement 而不是 Statement。

6. sql 语句数。

始终尽量减少发送到数据库的 sql 语句数。

【讨论】:

我强烈反对#3 - 在 Oracle 中,在单个事务中完成所有事情几乎总是更快,而不是将其拆分成更小的块。 Oracle 不是 SQL Server。 @a_horse_with_no_name - 是的,你是对的,大多数任务 Oracle 在一个 TX 中做得更好,但有些情况下任务对于当前的 REDO LOG 配置来说太大了,我说的是这种情况.在这种情况下,我通常将任务拆分为带有逻辑标记的较小 TX。无论如何,我是 Oracle 爱好者 :) 我写得更笼统——如果只是查询而不是好的(可能是只读的 tx)。但如果 DML - TX 的大小确实很重要,因为必须存储所有 TX 重做记录。 我基本同意这个答案中的优化。但是,在进行任何这些优化之前,我们应该做的第一件事是进行一些基本的分析,以找出耗时的逻辑。是查询本身没有有效编写吗?获取时间最长吗?等 @AdrianShum 你是对的,剖析给你诊断,然后你可以应用适当的药物,我同意【参考方案2】:

试试这个 resultSet.setFetchSize(100);

while(resultSet.next) ...

参数是应该从 每次往返的数据库

【讨论】:

以上是关于提高 JDBC 性能的主要内容,如果未能解决你的问题,请参考以下文章

使用JDBC如何提高访问数据库的性能?

在这种情况下,多线程如何帮助提高性能?

如何提高报表的取数性能

JDBC插入优化性能对比

JPA 批量插入不会提高性能

使用 PySpark JDBC 将数据帧写入 Azure SQL 数据库时性能下降