使用 UCanAccess 的相同 table 的后续查询性能低下

Low performance of subsequent query with the same table using UCanAccess

我正在尝试更新大约。分两步 table 中的 3800 行:

  1. 将所有值设置为 0
  2. 在循环中将其中一些设置为计算值

第一步很快(< 1 秒)。然而,第二步大约需要 3 秒。也许代码中存在一些问题,因为如果我 运行 没有第一步的代码或 运行 另一个 table 上的第一个查询,第二步也非常快(< 0.1 秒)。只有在相同 table.

的第一步之后执行时才慢
Class.forName("net.ucanaccess.jdbc.UcanaccessDriver");
conn = DriverManager.getConnection("jdbc:ucanaccess://" 
        + path +;singleconnection=true" ,"", ""); 

// First update (reset all values)
long start = System.currentTimeMillis();         
PreparedStatement ps2 = conn.prepareStatement("UPDATE mytab SET val = 0.0;"); 
ps2.executeUpdate();
ps2.close();
System.out.println("Update 1: " + (System.currentTimeMillis()-start)/1000.0 + " s");

// Second update
start = System.currentTimeMillis();
conn.setAutoCommit(false);
int count = 0;
PreparedStatement ps = conn.prepareStatement("UPDATE mytab SET val = ? WHERE id = ?;");

for(int j= 1; j <= 3600; j++){
      double value;
      // calculate some value ....
      value = 1.3;
      // update table under certain conditions
      if(true){
              ps.setDouble(1, value);
              ps.setInt(2, j);
              ps.addBatch();
              count++;
       }
       if(count > 200){
               ps.executeBatch();
               count = 0;
        }
}
ps.executeBatch();
conn.commit();
System.out.println("Update 2: " + (System.currentTimeMillis()-start)/1000.0 + " s");

为什么第二步这么久?如何避免这种情况?

(我已经问过同样的问题 here 但没有成功)。

编辑: 如果我尝试使用旧的 JDBC-ODBC 桥(和 Java 7)进行这两个查询,则第二个查询在第一个查询之后执行时非常快(< 1 秒)。所以我想一定是UCanAccess的问题。

原因是您有一个来自 mysql 的外部循环。调用程序不断调用相同的准备语句,但每个 call/execution 都有很多开销,即使实际语句很小。

理想的解决方案是将条件移动到SQL(如果可行)并让mysql处理它。在那种情况下,它通常非常快:

UPDATE mytab SET value=1.3 WHERE <condition>;

事实上,您可以将两者与 IF 语句结合起来(同样,如果 mysql 具有条件的正确数据:

UPDATE mytab SET value=IF(<condition>, 1.3, 0.0)

--- 编辑 --- 如果您有活跃的交易,那么第一个查询会搁置所有记录,并且每个后续查询都必须修改现有的(实时交易)。您将有 3800 个事务冲突。尝试在两个查询之间放置一个 COMMIT

正如问题评论中所确定的那样,最初的测试是欺骗性的,因为当第一个查询将值清零时,它迫使第二个查询更新每一行。但是,当省略第一个查询时,第二个查询只是 "updating" 具有现有测试值的行。由于这些行实际上并没有被更改,所以它们没有被刷新到数据库文件中,所以第二个查询 运行 快得多。对强制行由第二个查询更新的测试值的调整导致无论是否执行第一个查询都具有相同的性能。

至于更新的性能,您可能会发现它比直接更新每一行更快

  • 创建临时 table,
  • 将新值插入临时 table,
  • 将临时 table 合并到主 table 中,然后
  • 删除临时 table.

我刚刚试过了,它似乎 运行 只用了直接逐行更新主要 table 所需时间的四分之一:

StopWatch sw = new StopWatch();
sw.start();
try (Statement st = conn.createStatement()) {
    st.execute("CREATE TABLE zzzTemp (id LONG, val DOUBLE)");
}
double newDbl = ((Long) System.currentTimeMillis()).doubleValue();  // test data
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO zzzTemp (id, val) VALUES (?,?)")) {
    for (int i = 1; i <= 3600; i++) {
        ps.setInt(1, i);
        ps.setDouble(2, newDbl);
        ps.addBatch();
    }
    ps.executeBatch();
}
System.out.printf("Overall elapsed time: %d ms%n", sw.getTime());
try (Statement st = conn.createStatement()) {
    st.execute("MERGE INTO mytab m USING zzzTemp z ON m.id = z.id WHEN MATCHED THEN UPDATE SET m.val = z.val");
    System.out.printf("Overall elapsed time: %d ms%n", sw.getTime());
    st.execute("DROP TABLE zzzTemp");
    System.out.printf("Overall elapsed time: %d ms%n", sw.getTime());
}