【问题标题】:Faster batch loading of nested data更快地批量加载嵌套数据
【发布时间】:2013-09-09 18:06:59
【问题描述】:

我正在填充两个表,它们具有一对多的关系。

所以我在outer 中插入一行,获取该行的(自动增量主键)id,然后在inner 中插入 100 行(全部带有指向outer.id 的外键)。

然后我重复 50 次。对于outer 中的每个条目,我必须插入、读取id,然后插入到内部。

这很慢。大部分时间都花在将 100 行加载到inner 中。我怀疑如果我可以在一个批处理操作中将所有 50*100 行插入inner,它会快得多。但我不知道该怎么做 - 我怎样才能让外键工作?

其他人如何提高效率?

我正在使用 Java / Spring。这 100 行插入了 JdbcTemplate.batchUpdate()

public final void insert(final JdbcTemplate db,
                         final Iterable<DataBlock> data) {
    String insertSql = getInsertSql();
    String idQuery = getIdQuery();
    ItemRowMapper.IdRowMapper mapper = new ItemRowMapper.IdRowMapper();
    for (DataBlock block: data) {
        Object[] outer = block.getOuter();
        LOG.trace("Loading outer");
        db.update(insertSql, outer);
        LOG.trace("Getting index");
        // currently retrieve index based on natural key, but could use last index
        int id = db.query(idQuery, mapper, uniqueData(outer)).get(0);
        LOG.trace("Getting inner");
        List<Object[]> inner = block.getInner(id);
        // most time spent here
        LOG.trace(format("Loading inner (%d)", inner.size()));
        innerTable.insert(db, inner);
    }
}

和伪SQL:

create table outer (
    integer id primary key autoincrement,
    ...
);

create table inner (
    integer outer references outer(id),
    ...
);

更新 - 以下内容似乎适用于 Spring 3.1.1 和 Postgres 9.2-1003.jdbc4。

/**
 * An alternative implementation that should be faster, since it inserts
 * in just two batches (one for inner and one fo router).
 *
 * @param db A connection to the database.
 * @param data The data to insert.
 */
public final void insertBatchier(final JdbcTemplate db,
                                 final AllDataBlocks data) {
    final List<Object[]> outers = data.getOuter();
    List<Integer> ids = db.execute(
            new PreparedStatementCreator() {
                @Override
                public PreparedStatement createPreparedStatement(
                        final Connection con) throws SQLException {
                    return con.prepareStatement(getInsertSql(),
                            Statement.RETURN_GENERATED_KEYS);
                }
            },
            new PreparedStatementCallback<List<Integer>>() {
        @Override
        public List<Integer> doInPreparedStatement(final PreparedStatement ps)
                throws SQLException {
            for (Object[] outer: outers) {
                for (int i = 0; i < outer.length; ++i) {
                    setParameterValue(ps, i + 1,
                            SqlTypeValue.TYPE_UNKNOWN, outer[i]);
                }
                ps.addBatch();
            }
            ps.executeBatch();
            RowMapperResultSetExtractor<Integer> ids =
                    new RowMapperResultSetExtractor<Integer>(
                            new ItemRowMapper.IdRowMapper());
            try (ResultSet keys = ps.getGeneratedKeys()) {
                return ids.extractData(keys);
            }
        }
    });
    innerTable.insert(db, data.getInner(ids));
}

【问题讨论】:

  • 我不明白。为什么不将outer 中的所有记录一次插入,然后将inner 中的所有记录一次插入?你为什么不发布一些代码?
  • 如果我可以检索外部表中所有插入的索引,我可以这样做。有没有办法在一系列值上检索自动增量键?我必须手动管理密钥生成吗?可能有多个线程正在向这些表添加/删除数据。
  • 对 java 代码不了解,但从数据库的角度来看,您必须以基于集合的方法执行此操作,而不是逐行执行,这听起来就像您目前正在做的那样。您可以将所有数据转储到 postgre 中的临时表中并使用临时表作为源执行插入吗?这将一次性插入所有数据,这比许多微小的插入便宜。
  • @JustBob(谢谢)忽略java,您将如何设置以便内部(或临时)表具有对外部表的外键引用?这是我的根本问题。内表的每 100 行(或多或少)引用一个不同的外行。并且 id 是自动递增的。我想我需要自己管理密钥。
  • 我添加了一些 sql 表以防万一。

标签: java sql spring postgresql


【解决方案1】:

我对@9​​87654322@ 不太熟悉,但假设它类似于 JDBC,我会使用与以下代码类似的东西(我可能会将其分解为多种方法):

private static final int BATCH_SIZE = 50;

public void addBatch(Connection connection, List<Outer> outers) {

  PreparedStatement outerInsertStatement = connection.prepareStatement("...", Statement.RETURN_GENERATED_KEYS);
  PreparedStatement innerInsertStatement = connection.prepareStatement("...", Statement.RETURN_GENERATED_KEYS);

  List<Integer> outerIds = new ArrayList<Integer>();

  for(Outer outer : outers) {
    outerInsertStatement.setParameter(...);
    ...
    outerInsertStatement.setParameter(...);

    outerInsertStatement.addBatch();
  }

  outerInsertStatement.executeBatch();
  //Note, this line requires JDBC3
  ResultSet primaryKeys = outerInsertStatement.getGeneratedKeys();
  while(!primaryKeys.isAfterLast()) {
    outerIds.add(primaryKeys.getInt(0));
  }

  for(int i = 0; i < outers.size(); i++) {
    Outer outer = outers.get(i);
    Integer outerId = outerIds.get(i);
    for(Inner inner : outer.getInners()) {
      //One of these setParameter calls would use outerId
      innerInsertStatement.setParameter(...);
      ...
      innerInsertStatement.setParameter(...);
      innerInsertStatement.addBatch();

      if( (i+1) % BATCH_SIZE == 0) {
        innerInsertStatement.executeBatch();
      }
    }
    innerInsertStatement.executeBatch();
  }
}

【讨论】:

  • 太棒了。我不知道 getgeneratedkeys 存在。谢谢。 (环顾四周,它似乎需要一个最新的 jdbc 和 postgres 库,但这不应该成为问题)。
  • 你有这种方法来处理 Postgres、批量加载和生成的密钥吗?我试过了(在问题的“更新”部分查看我的代码),但似乎 postgres 驱动程序不支持这个(它在 jdbc 标准中是可选的 - stackoverflow.com/questions/15684297/…
  • 啊,不,对不起。我倾向于在 MySQL 中工作,而没有在 Postgres 中工作过。在准备你的陈述时,你可能需要con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)
  • 我读过,然后忘记了。哑的。但你更厉害。这似乎工作:o)
  • 不错的解决方案:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-25
  • 2021-12-26
  • 2021-06-14
  • 1970-01-01
相关资源
最近更新 更多