【问题标题】:Updating a bunch of rows is it transaction based or row based?更新一堆行是基于事务还是基于行?
【发布时间】:2015-12-27 10:40:46
【问题描述】:

我有一个表,其中有一列需要不断地重新计算,我希望这个表是可扩展的。用户也必须能够在上面写字。

如果没有服务器和并发用户,很难测试这种类型的东西,至少我不知道如何。 那么这两个选项之一是否可行?

@ApplicationScoped
public class Abean {
   @EJB
   private MyService myService;
   @Asynchronous
   public void computeTheData(){
      long i = 1;
      long numberOfRows = myService.getCountRows(); // gives the number of row in the table
      while(i<numberOfRows){
        myService.updateMyRow(i);
      }
      computeTheData(); // recursion so it never stops, I'm wondering if this wouldn't spawn more threads and if it would be an issue.
   }
}

public class MyService implements MyServiceInterface{
    ...
    public void updateMyRows(int row){
       Query query = em.createQuery("SELECT m FROM MyEntity WHERE m.id=:id");
       Query.setParameter("id", row);
       List<MyEntity> myEntities = (MyEntity) query.getResultList();
       myEntity.computeData();
    }
}

VS

@ApplicationScoped
public class Abean {
   @EJB
   private MyService myService;
   @Asynchronous
   public void computeTheData(){
      myService.updateAllRows();
   }
}

public class MyService implements MyServiceInterface{
        ...
    public void updateAllRows(int page){
       Query query = em.createQuery("SELECT m FROM MyEntity");
       List<MyEntity> myEntities = (MyEntity) query.getResultList();
       myEntity.computeData();
    }
}

这些可行吗?我用的是mysql,表的引擎是innoDB。

【问题讨论】:

  • 我不确定如何理解这个问题。你想达到什么目的?您是否想测试性能并且您想编写一些测试代码,以更新数据库中的每一行,甚至多次(根据示例 1 中的无限递归)?
  • @OndrejM 我有一列是 hot_score,它是时间的函数(reddit 算法)。我希望能够告诉我的数据库选择最高热分的前 20 名。但是,由于它是时间的函数,我需要不断更新它。但如果我不断更新它,那么用户就无法在上面写字。所以我正在努力寻找解决方案。
  • 好的,我知道您需要支持并发更新 - 来自频繁的后台作业和来自用户的更新。如果您使用乐观锁定,您可能会遇到问题。我建议在事务期间使用悲观锁定来锁定已修改的实体。如果您希望查看代码示例,我稍后会在完整的答案中进行解释。
  • @OndrejM 非常感谢。实际上我正在学习 Cassandra 将我的数据库迁移到它。你认为这会是一个好的解决方案吗?

标签: mysql jpa concurrency eclipselink jpql


【解决方案1】:

您应该在更新之前使用悲观锁定来锁定修改的行,这样用户的手动修改不会与后台更新发生冲突。如果您不使用锁定,如果用户的修改与修改了同一行的后台作业发生冲突,有时会回滚。

此外,对于悲观锁定,如果您的用户的事务等待获取锁的时间超过超时发生时间,则她可能会遇到回滚。为了防止这种情况,您应该使所有使用悲观锁的事务尽可能短。因此,后台作业应该为每一行或一小组行创建一个新事务,如果它可能运行的时间超过合理的时间。只有在事务完成后才会释放锁(用户将等待锁被释放)。

MyService 的外观示例,在单独的事务中运行每个更新(实际上,您可以在单个事务中批量运行多个更新,将 id 列表或范围作为参数传递给 updateMyRows):

public class MyService implements MyServiceInterface{
        ...
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) // this will create a new transaction when running this method from another bean, e.g. from Abean
    public void updateMyRows(int row){
       TypedQuery<MyEntity> query = em.createQuery(SELECT m FROM MyEntity WHERE m.id=:id", MyEntity.class);
       query.setParameter("id", row);
       query.setLockMode(LockModeType.PESSIMISTIC_WRITE); // this will lock all entities retrieved by the query
       List<MyEntity> myEntities = query.getResultList();
       if (!myEntities.isEmpty()) {
         myEntities.get(0).computeData();
       }
    }
}

当你在where条件中只使用id时,你可以考虑em.find(row, MyEntity.class, LockModeType.PESSIMISTIC_WRITE).computeData()而不是使用查询(在em.find()之后添加空指针检查)

其他说明:

从问题中不清楚您如何触发后台作业。正如您在示例中所写的那样,无限地运行作业,一方面不会创建额外的线程(当您在同一个 bean 上调用方法时,不会递归地考虑注释)。另一方面,如果出现异常,您的后台作业至少应该处理异常,使其不会停止。您可能还想在后续执行之间添加一些等待时间。

最好将后台作业作为计划作业运行。一种可能的选择是@Schedule 注释而不是@Asynchronous。您可以指定作业在后台执行的频率。然后最好检查你的工作的开始,之前的执行是否完成。 Java EE 7 的另一个选项是使用ManagedScheduledExecutorService 以指定的时间间隔定期触发后台作业。

【讨论】:

  • 谢谢,这对我有很大帮助。当您为新事务编写 TransactionAttributeType.REQUIRES_NEW 时,我想如果我的服务是无状态的,则不需要这样做?我认为无状态 bean 中的函数是事务的入口点,函数的结束是事务的结束(它是在后台自动完成的)。
  • 我将事务属性放在那里以强制始终创建新事务。在您的代码中,可能没有必要,因为您是从非事务性 bean 调用服务。但是,如果您从另一个 EJB 调用 MyService 就很重要了。默认情况下,它将重用调用者的事务,并且所有锁将由单个事务持有。当代码对事务的创建方式很敏感时,最好是明确的并且总是需要一个新的事务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-28
  • 1970-01-01
  • 1970-01-01
  • 2019-04-11
  • 2012-08-23
  • 1970-01-01
相关资源
最近更新 更多