16

我正在研究应用程序的延迟敏感部分,基本上我将接收网络事件转换数据,然后将所有数据插入数据库。分析后,我发现基本上我所有的时间都花在了保存数据上。这是代码

private void insertAllData(Collection<Data> dataItems)
{
    long start_time = System.currentTimeMillis();
    long save_time = 0;
    long commit_time = 0;
    Transaction tx = null;
    try
    {
        Session s = HibernateSessionFactory.getSession();
        s.setCacheMode(CacheMode.IGNORE);
        s.setFlushMode(FlushMode.NEVER);
        tx = s.beginTransaction();
        for(Data data : dataItems)
        {
            s.saveOrUpdate(data);
        }
        save_time = System.currentTimeMillis();
        tx.commit();
        s.flush();
        s.clear();
    }
    catch(HibernateException ex)
    {
        if(tx != null)
            tx.rollback();
    }
    commit_time = System.currentTimeMillis();
    System.out.println("Save: " + (save_time - start_time));
    System.out.println("Commit: " + (commit_time - save_time));
    System.out.println();
}

集合的大小始终小于 20。这是我看到的时序数据:

Save: 27
Commit: 9

Save: 27
Commit: 9

Save: 26
Commit: 9

Save: 36
Commit: 9

Save: 44
Commit: 0

这让我很困惑。我认为save应该很快,所有时间都应该花在commit. 但显然我错了。我也试过删除事务(它不是真的必要),但我看到更糟糕的时候......我设置了 hibernate.jdbc.batch_size=20...

我可以期望每秒收到多达 500 条消息,因此我需要将单个消息处理时间控制在 20 毫秒以下。

我需要这个操作尽可能快,理想情况下只有一次往返数据库。我怎样才能做到这一点?

4

3 回答 3

17

将您的主键生成远离服务器端自动增量。您的 Java 代码必须负责生成 PK 以避免往返。

为了获得不错的批量插入性能,您需要一种不需要在每次调用 saveOrUpdate 时都访问数据库的方法。使用 UUID 作为主键,或者实现 HiLo可以帮助实现这一点。否则,实际上不会进行批量插入。

为了兼顾性能和与其他外部系统的互操作性,pooledpooled-lo优化器是最佳选择。

于 2010-06-12T03:21:57.117 回答
3

老实说,我不知道从你的测试和你展示的“测量”中可以合理地得出什么结论(我怀疑热身的开销很大,收集非常小,样本非常小)。

无论如何,我可以告诉您,您当前的代码不会扩展,并且您很可能会在传递更大的集合时爆炸 Session。您需要定期刷新和清除会话(如果批量大小为 20,则每 20 条记录)。

实际上,我建议阅读整个第 13 章。批处理

于 2010-06-12T02:13:26.817 回答
0

一些基本的东西:

  • 您是否有没有索引的触发器或外键约束?
  • 你有批处理驱动程序吗?
  • 您的驱动程序是否处于批处理模式(参见 Pascal 参考中的 hibernate.jdbc.batch_size)?
  • 您的表上有任何索引(如果您有很多索引,有时会减慢插入速度)?

批处理是 JDBC 2.0 的一部分,它允许您在一个“批处理”中执行多个语句;这个想法是减少往返延迟(您可以在每个事务中执行多个批次)。

Statement stmt = dbCon.createStatement("insert into DataTable values (?,?,?)");
stmt.setInt(1, x1); stmt.setInt(2, x2), stmt.setString(3, "some value");
stmt.addBatch();
...
stmt.setInt(1, x2); stmt.setInt(2, x3), stmt.setString(3, "some other value");
stmt.addBatch();

stmt.executeBatch();
dbCon.commit();

您可能可以将其用作基准测试。我还将查看 hibernate 生成的 SQL,以查看它是否在每次插入时执行查询以获取生成的 Id。

于 2010-06-12T02:52:47.263 回答