【问题标题】:How expensive is committing a hibernate transaction?提交休眠事务有多昂贵?
【发布时间】:2011-04-05 19:26:49
【问题描述】:

我有以下用例,我通过 JMS 通过实体的唯一属性(不是 PK)接收有关实体的消息,它要求我更新实体的状态:

HibernateUtil.beginSession();  
HibernateUtil.beginTransaction();  
try{  
  Entity entity = dao.getEntityByUniqueProperty(propertyValue);  
  if (entity==null){  
    entity = dao.addEntityByUniqueProperty(propertyValue)  
  }  
  entity.setSomeProperty(otherPropertyValue);
  HibernateUtil.commitTransaction();
} catch (ConstraintViolationException e){  
  HibernateUtil.rollbackTransaction();  
  //Do other things additionally  
} catch (StaleStateObjectException e){  
  HibernateUtil.rollbackTransaction();  
  //Do other things additionally  
} finally {  
  HibernateUtil.closeSession();  
}

在这个用例中,我必须为我尝试更新的实体尚未创建这一事实做好准备,因此我要求创建这样的实体(它的模板要精确地具有唯一属性) 然后我改变它。 我的困境如下: 一方面,我有两个明显不同的块,我应该在适当的情况下使用不同的 catch 子句,但将其视为最终情况,即我查询时实体不存在,但稍后我尝试创建它时是否存在(因此 ConstraintViolationException ) 是不应该经常发生的事情,并且插入因为中间的额外提交/开始事务看起来很紧凑。

我主要关心的是在提交/开始时完成的会话同步和 JDBC 连接对性能的额外影响。
我错了吗?我是否在不应该的地方寻找优化?我错过了什么吗?
提前致谢

【问题讨论】:

    标签: java performance hibernate transactions


    【解决方案1】:

    首先,您需要一笔交易。没有它,上面的代码将无法工作,因为这可能会发生:

    • 线程 1 创建了唯一实例
    • 线程 2 获得唯一实例
    • 线程 2 设置其他属性
    • 线程 1 设置其他属性
    • 线程 1 刷新缓存
    • 线程 2 刷新缓存

    问题:在这种情况下数据库是否一致?在类似情况下会(不)一致吗?

    始终使用事务。数据库针对它进行了优化。如果您有问题,请开始考虑您的设计。就像您必须每秒处理数千条消息并且您的性能工具显示此代码已成为瓶颈。不要相信你的直觉。

    【讨论】:

    • 除非我误解了 OP 询问 txn 有多贵,因为他正在考虑抛出 ConstraintViolationException 的情况,他必须通过单独的/新事务处理极端情况。而不是是否完全使用 txns。
    • @MikeQ:你是对的,但这不是问题所在。他可能会获得更好的性能,但同时会破坏他的数据。
    【解决方案2】:

    休眠事务几乎只是数据库事务的包装器。所以它和数据库事务一样昂贵。

    与优化一样,通常最好有清晰和安全的代码,而不是试图获得额外的 1% 的性能。但我不知道你的用例。如果上述内容每秒被调用几次,那么不要担心性能。如果它每秒被调用几百次,那么它可能是个问题。

    如果您遇到性能问题,请测量/计时/分析代码,直到找到问题为止。通常你可以假设问题出在某个地方,而实际上它在其他地方。

    在您上面的情况下,我会执行以下操作

    • 在您的代码周围放置一个 while 循环(所有代码,包括会话打开/关闭)
    • 如果它命中了ConstraintViolationException 块日志和continue,那么与其编写一些额外的逻辑,不如让它重试,然后它会找到另一个事务添加的新行并适当地更新。
    • 它工作正常然后break 退出循环

    编辑:我会怎么做……

    // loop as it is possible we get a retryable exception, see below
    while (true){
        HibernateUtil.beginSession();
        HibernateUtil.beginTransaction();  
        try{  
          Entity entity = dao.getEntityByUniqueProperty(propertyValue);  
          if (entity==null){  
            entity = dao.addEntityByUniqueProperty(propertyValue)  
          }  
          entity.setSomeProperty(otherPropertyValue);
          HibernateUtil.commitTransaction();
    
          // success
          break;
    
        } catch (ConstraintViolationException e){  
          HibernateUtil.rollbackTransaction();
    
          // this is ok, another txn must have added the entity at the same time
          // try again and it will find the entity this time
          continue;
    
        } catch (StaleStateObjectException e){  
          HibernateUtil.rollbackTransaction();  
          //Do other things additionally  
    
        } finally {  
          HibernateUtil.closeSession();  
        }
    }
    

    【讨论】:

    • +1 表示:“...您通常可以假设问题出在某个地方,而实际上它在其他地方。”确实如此。关于“休眠事务几乎只是数据库事务的包装器。所以它像数据库事务一样昂贵。”数据库事务有多贵?大致?谢谢
    • 您可能每秒可以执行几百个小型数据库事务。球场。
    【解决方案3】:

    无论你做什么,写操作都不能在事务之外进行,如果没有正在进行的事务,Hibernate 会报错并抛出异常。所以这里别无选择。

    现在,您的用例 - findOrCreate() - 并非微不足道。正如您所提到的,您可能会面临竞争条件:

    T1:开始发送; T2:开始发送; T1:getEntityByUniqueProperty("foo"); //返回空 T1:getEntityByUniqueProperty("foo"); //返回空 T1: addEntityByUniqueProperty("foo"); T1:提交; //插入的行 T1: addEntityByUniqueProperty("foo"); T2:提交; //约束违反

    所以你必须要么

    1. 同步代码(这只是一个选项IF你在单个JVM中运行)~or~
    2. 锁定整个表(哎哟!)~or~
    3. 处理它并实施某种重试机制。

    就个人而言,我会选择选项 3。就性能而言,这是最佳选择,而且实际上是一种常见模式,尤其是在处理消息传递和高并发时。

    【讨论】:

      【解决方案4】:

      我想我实际上已经找到了我的用例的具体问题,那就是仅在实际需要时打开事务,因此我避免了过早的性能困境:

      try {
          HibernateUtil.beginSession();
          Entity entity = dao.getEntityByUniqueProperty(propertyValue);
          if (entity==null){
              HibernateUtil.beginTransaction();
              try {
                  entity = dao.addEntityByUniqueProperty(propertyValue)
                  HibernateUtil.commitTransaction();
              } catch (ConstraintViolationException e){
                  HibernateUtil.rollbackTransaction();
                  HibernateUtil.closeSession();
                  HibernateUtil.beginSession();
                  entity = dao.getEntityByUniqueProperty(propertyValue);
                  //Do other things additionally
              }
          }
          entity.setSomeProperty(otherPropertyValue);
          HibernateUtil.commitTransaction();
      } catch (StaleStateObjectException e){
          HibernateUtil.rollbackTransaction();
          //Do other things additionally
      } finally {
          HibernateUtil.closeSession();
      }
      

      这将使我能够本地化每个交易中的特定风险,并避免在不需要时提交和打开交易。 感谢您的 cmets。

      【讨论】:

      • 我不建议这样做,最佳做法是即使选择也始终进行交易,请参阅 Aaron 的回答。另外,您会发现 JDBC 驱动程序无论如何都可能发出事务调用。老实说,除非您尝试每秒进行数百次,否则这不太可能成为问题。我将用我将如何解决这个问题来编辑我的答案。
      • @Mike 这不是真的,不使用事务进行只读查询完全没问题。
      猜你喜欢
      • 1970-01-01
      • 2018-05-06
      • 1970-01-01
      • 2013-05-04
      • 1970-01-01
      • 1970-01-01
      • 2010-11-14
      • 2011-12-06
      • 2019-09-22
      相关资源
      最近更新 更多