从HashMap高效批量插入/复制到表中

时间:2019-05-01 17:42:45

标签: java postgresql

任务:

给出以下HashMap结构:Map<String, Map<String, String>> mainMap = new HashMap<>()

我要将{em> inner INSERT的每个 COPYMap放入数据库中自己的单元格中。 / p>

  • size()中的mainMap(如果为50,000)。
  • 内部size()的{​​{1}}是50。
  • 要插入的表有50列。
  • 每个列的标题是内部Map的键。

编辑:最初,用户上载具有50列中的35列的大型电子表格。然后,我使用各种格式“清理”该数据,并为每个Map条目将自己的15对新数据添加到innerMap中。如果不进行清理/格式化/添加,就无法直接从用户的源文件mainMap到数据库。

一旦我完成了电子表格的迭代并构建了COPY,那便是我需要有效地插入数据库表的时候了。

研究:

read认为mainMap是最初批量填充表的最佳方法,但是我对我的要求是否支持该命令感到困惑。

This post指出Postgres的查询的Prepared Statement参数限制为34464。

我假设我总共需要50 x 50,000 = 2,500,000个参数。 这相当于〜73个独立查询!

问题:

  • 这里COPY而不是所有这些参数是正确的方法吗?
  • 如果是,我是否将COPY的值转换为HashMap文件,将其保存在Web应用程序服务器的磁盘上,然后在我的.sql命令中引用它,然后删除临时文件?还是可以直接将串联的COPY传递给它,而又不会冒着SQL注入的风险?

该命令经常发生,因此需要进行优化。

我找不到将Java对象转换为兼容的Postgres文本文件格式的任何示例,因此任何反馈都可以。

您将如何解决这个问题?

其他信息:

我的表已经存在,并且不能删除,因为它是我的Web应用程序的后端,并且在任何给定时间都已连接多个用户。

我知道在使用String之前临时删除索引可以提高性能,但是我一次只需要插入或复制最多50,000行,而不需要数百万行。

StackExchange告诉我在这里问。

2 个答案:

答案 0 :(得分:3)

虽然Java当然不是执行此类ETL的最佳选择,但使用标准INSERT语句和预先准备好的查询确实可以并且开销很小:

conn.setAutoCommit(false);
PreparedStatement stmt = conn.prepareStatement(
        "INSERT INTO my_table (col_a, col_b, ...)"
        + " VALUES (?, ?, ...)");
int batchSize = 1000;
int rows = 0;
for (Map<String, String> values : mainMap.values()) {
    int i = 0;
    stmt.setString(++i, values.get("col_a"));
    stmt.setString(++i, values.get("col_b"));
    // ...
    stmt.addBatch(); // add the row to the batch
    if (++rows % batchSize == 0) {
        // batch-sizing: execute...
        stmt.executeBatch();
    }
}

if (rows % batchSize != 0) {
    // a last execution if necessary...
    stmt.executeBatch();
}
conn.commit(); // atomic action - if any record fails, the whole import will fail

或者,您可以将Map写出到文件中并使用CopyManager,但是我严重怀疑这样做的速度是否比批处理插入的速度要快(不过,成百万行的记录会有所不同)。 / p>

答案 1 :(得分:2)

COPY确实可能是初始批量上传的推荐方法,但是考虑到您的初始数据存储在Java Map中的内存中,因此存在局限性:

  • 首先,它希望从文件(对于服务器是本地的,并且可由其用户读取),程序(还是在服务器上本地执行)或通过STDIN进行加载。这些选项都不对JDBC连接特别友好。
  • 第二,即使您可以以这种格式准备数据(例如,假设您在同一台机器上准备这样的文件),您仍然需要将Java内存中保存的数据转换为预期格式为COPY。使用COPY可能不值得进行这种处理。

我会创建一个PreparedStatement来插入您的50列,然后针对Map中的每个mainMap.values()(即每次50列)重复执行该准备好的语句。 / p>

您可以使用executeBatch()提高速度。就是说,我不会一次执行全部50000,而是分批执行。

我会做这样的事情:

    int BATCH_SIZE = 100;

    List<String> keyNames = new ArrayList<>();

    int i = 0;
    try (PreparedStatement ps = conn
            .prepareStatement("INSERT INTO xyz (col1, col2, ...) VALUES (?, ?, ...)")) {
        for (Map<String, String> rowMap : mainMap.values()) {
            int j = 1;
            // You need the keynames to be in the same order as the columns
            // they match.
            for (String key : keyNames) {
                ps.setString(j, rowMap.get(key));
                j++;
            }
            ps.addBatch();

            if (i > 0 && i % BATCH_SIZE == 0) {
                ps.executeBatch();
            }
            i++;
        }
        if (i % BATCH_SIZE != 1) {
            // More batches to execute since the last time it was done.
            ps.executeBatch();
        }
    }