【问题标题】:high performance hibernate insert高性能休眠插入
【发布时间】:2011-03-02 21:39:05
【问题描述】:

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

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 毫秒以下。

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

【问题讨论】:

  • 顺便说一句,在使用FlushMode#NEVER 时,您不应该在flush() 之后commit() 吗?
  • @Pascal Thivent。我不知道:-)
  • 好吧,阅读Transaction#commit()的javadoc :)

标签: java performance hibernate3


【解决方案1】:

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

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

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

【讨论】:

  • 我目前正在使用 oracle 序列来生成 id。这不可行吗?
  • 就是这样!,我删除了序列并向启动添加了一个查询,以确定从哪里开始序列和 bam,7.5 倍的加速使其远低于我的阈值。
  • Hibernate 具有生成 ID 序列的能力,您不必自己编写该代码。如果你使用注解,你可以用@GeneratedValue 添加@Id。
  • 贾斯汀提出了一个重要的观点,我应该提到这一点...... Hibernate 开箱即用地处理这两种方法,只需要一点配置/注释(不需要代码)。如果你想要 hilo,你只需要创建一个表并进行一些配置,其余的由 Hibernate 处理docs.jboss.org/hibernate/stable/core/reference/en/html/…
  • 仅供参考,如果您有 serial 并想保留它,只要您有其他识别记录的方法就可以了。只是不要映射serial 即在休眠中忽略它根本存在,数据库仍会生成并存储它。当然,您需要将其他内容映射为休眠的 id。
【解决方案2】:

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

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

其实我推荐阅读全文Chapter 13. Batch processing

【讨论】:

  • 我在上面的代码中刷新和清除会话。集合永远不会超过 20 个。
【解决方案3】:

一些基本的东西:

  • 您是否有触发器或外键 没有索引的约束?
  • 您有批处理驱动程序吗?
  • 您的驱动程序是否处于批处理模式(参见 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。

【讨论】:

  • 如何判断我是否有批处理驱动程序?
猜你喜欢
  • 2017-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-01
相关资源
最近更新 更多