【问题标题】:Delete huge number of rows from an EJB Timer从 EJB 计时器中删除大量行
【发布时间】:2010-02-17 14:10:56
【问题描述】:

我需要从 EJB 计时器中的表中删除数百万行。 问题是计时器的事务超时时间为 90 秒,所以我应该将工作分成小块。

由于我不知道在 90 秒内可以删除多少行,所以算法应该循环并一次删除几行,直到时间快到为止。

问题是: 如何在 JPA 中优雅地限制要删除的行数? 对时间戳早于某个日期的所有行进行删除。

我想有可能找到第 1000 个最旧的行和DELETE WHERE timestamp <= {1000th-oldest-row.timestamp},但是,这不是很优雅,我必须到达 1000 中的最后一行才能获得时间戳。

其次,如果90秒后桌子不干净,计时器应该立即再次触发。这很容易解决,但又不是很优雅。

【问题讨论】:

    标签: java sql jpa timer ejb


    【解决方案1】:

    使用现有解决方案,您仍将面临交易过期问题。

    诀窍是在单独的事务中执行每个块,如下面的伪代码所示。

    @Entity
    
    @NamedQueries ( value = {
        @NamedQuery (
            name = pagedDeleteExpiredItems
            query=    DELETE FROM MyTable
                WHERE (<table key>) IN (
                    SELECT <table key> FROM (
                    SELECT ROWNUM AS row_num, <table key> FROM MyTable
                    WHERE timestamp <= :currentTime
                    )
                    WHERE row_num < :pageSize
                )
        )
    })
    
    public class MyEntity {
        @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
        int doPagedDeleteExpiredItems(Date currentTime, int pageSize) {
            Query query = em.createNamedQuery("pagedDeleteExpiredItems");
            query.setParameter("currentTime", currentTime);
            query.setParameter("pageSize", pageSize);
            int deleteCount = query.executeUpdate();
            return deleteCount;
        }
    }
    
    
    @EJBTimer
    public class DeleteExpiredItemsTimer {
    
        @EJB(beanName = "MyEntity")
        MyEntity myEntity;
    
        @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
        void handleTimeout(Timer timer) {
            Date currentTime = getCurrentTime()
            int pageSize = 100
            int deleteCount;
            do {
                myEntity.doPagedDeleteExpiredItems(currentTime, pageSize);
            } while(deleteCount>0);
        }
    }
    

    【讨论】:

    • 好吧,在我的约束下,交易已经解决了。但是你的解决方案很漂亮。
    【解决方案2】:

    我们有类似的要求,下面是我们解决它的方法。我使用的是 EJB 3.0。

    1. 应用程序时启动计时器。服务器在ServletContextListener 中启动(或部署模块)。
    2. 当计时器触发时,它会处理多达 100 行未决的行。然后,您需要对查询结果进行排序并限制行数。
    3. 如果有 100 行,计时器将使用0ms 安排下一次超时。即,事务被提交,并且计时器在新事务中再次触发。
    4. 如果少于 100 行,计时器会在 90sec 中安排下一次超时。

    如果有 250 行,计时器会依次触发 3 次。如果恰好有 100 行要处理,则只有一个小问题,在这种情况下,计时器会按顺序触发两次,但第二次触发实际上什么也不处理。但总而言之,它工作正常。

    【讨论】:

    • 是的,这正是我想做的。但是如何限制优雅删除的行数呢?
    • 类似DELETE FROM MyTable ORDER BY timestamp DESC LIMIT 100。确切的 SQL 语法将取决于您使用的数据库。使用 JDBC,您可以获取受DELETE 影响的行数,然后按照我的描述进行操作。
    【解决方案3】:

    我在 SQL 中使用的一个技巧是删除 TOP 1000(或 100 或 10000,具体取决于页面中的平均行数),如下所示:

    DELETE top 1000 WHERE timestamp <= @ExpirationDate
    

    重复调用此方法,直到没有行被删除(使用@@rowcount 检查)或者你的时间用完了。这种技术可以在 JPA 中实现吗?

    【讨论】:

      【解决方案4】:

      通过获取符合清理条件的行的排序列表并使用与 setMaxResults(int) 相同的 setFirstResult(int) 解决了该问题。这样我就可以从最旧的开始大约 maxCount 步骤的项目排序。

      Query expired = dm.createNamedQuery("getExpiredElements");
      expired.setParameter("currentTime", getCurrentTime());
      expired.setMaxResults(maxCount);
      expired.setFirstResult(maxCount);
      @SuppressWarnings("unchecked")
      List<Item> expiredChunk = (List<Item>) expired.getResultList();
      long lastChunkEndTime = expiredChunk.get(0).getEndTime();
      Query query = em.createNamedQuery("deleteExpiredItems");
      query.setParameter("currentTime", lastChunkEndTime);
      int result = query.executeUpdate();
      return result >= maxCount;
      

      如果应该再次执行,该函数(至少)返回 true。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-12-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多