2014-05-02 12 views
0

我有一个小表(< 100行),它包含一个有序的项目列表。我想随机排列这些项目。我想这样做的方式是选择最近使用的5个项目,并随机选择其中一个。但是,我只想偶尔做一次。防止一个动作被不同的服务器多次调用

我正在考虑使用存储过程来完成此操作,然后查询变得像SELECT TOP 1 * FROM myTable ORDER BY LastUsedDate DESC一样。

不幸的是,这个解决方案并不好。如果每个排列之间的时间间隔(每次运行存储过程)都是可变的,则每隔X分钟运行的SQL-Server作业将不起作用。如果我让我的服务器执行排列,多个服务器可能最终会进行排列。

这是我的想法是做服务器上的逻辑:

  1. 从数据库获取最近使用的项目
  2. 如果它的时间来进行置换,尽量放在桌上
  3. 得到一个锁
  4. 如果它已经被锁定,这意味着另一台服务器已经进行置换(转到1)
  5. 进行置换
  6. 返回最近使用的项目。

但是,我可以想象,锁定表并不是一个很好的解决方案。所以我在寻找建议:)。

我在Hibernate服务器上使用Java。

谢谢!

更新:

最后我想使用Hibernate而不是有一个存储过程(更容易调试,易于推动)锁定行。但是,我不认为hibernate正确锁定了necassary行。这里是我的代码:

Session s = sessionFactory.openSession(); 
Transaction tx = null; 
try { 
    tx = s.beginTransaction(); 

    //Check whether the most recent tournament is expired or not. If it's not, abort (another server already updated it) 
    TournamentTemplateRecord lastActiveTournament = (TournamentTemplateRecord) s.createCriteria(TournamentTemplateRecord.class) 
    .addOrder(Order.desc("lastUse")) 
    .setMaxResults(1) 
    .uniqueResult(); 

    long startTime = lastActiveTournament.getLastUse().getTime(); 
    long tournamentDurationMillis = lastActiveTournament.getDurationInSec() * 1000; 
    if ((startTime + tournamentDurationMillis) < System.currentTimeMillis()){ 
     //Tournament is still active and valid. Abort. 
     System.out.println("Tournament is still active"); 
     tx.rollback(); 
     return; 
    } 

    // Fetch the 5 least recently used tournaments 
    List<TournamentTemplateRecord> leastRecentlyUsedTournaments = s.createCriteria(TournamentTemplateRecord.class) 
    .addOrder(Order.asc("lastUse")) 
    .setMaxResults(5) 
    .setLockMode(LockMode.PESSIMISTIC_WRITE) 
    .setTimeout(0) //If rows are locked, another server is probably already doing this. 
    .list(); 

    Random rand = new Random(); 

    // Pick one at random 
    TournamentTemplateRecord randomTournament = leastRecentlyUsedTournaments.get(rand.nextInt(leastRecentlyUsedTournaments.size())); 

    randomTournament.setLastUse(new Date()); 

    s.update(randomTournament); 

    tx.commit(); 
} catch (Exception e) { 
    if(tx != null) { 
     tx.rollback(); 
    } 
} finally { 
    s.close(); 
} 

但是,休眠不会产生SELECT ... FOR UPDATE NOWAIT。有任何想法吗?

这里的生成HQL:

Hibernate: 
    WITH query AS (select 
     ROW_NUMBER() OVER (
    order by 
     this_.lastuse desc) as __hibernate_row_nr__, 
     this_.combattemplateid as id89_0_, 
     this_1_.combattypeid as combatty2_89_0_, 
     this_1_.combattargetid as combatta3_89_0_, 
     this_1_.resourcenameid as resource4_89_0_, 
     this_1_.resourcedescriptionid as resource5_89_0_, 
     this_1_.rewardloottemplateid as rewardlo6_89_0_, 
     this_1_.combatcontainertypeid as combatco7_89_0_, 
     this_.requirementtemplateid as requirem2_90_0_, 
     this_.assetid as assetid90_0_, 
     this_.durationinsec as duration4_90_0_, 
     this_.lastuse as lastuse90_0_ 
    from 
     tournament_tournamenttemplate this_ 
    inner join 
     readyforcombat_combattemplate this_1_ 
      on this_.combattemplateid=this_1_.id) SELECT 
      * 
    FROM 
     query 
    WHERE 
     __hibernate_row_nr__ BETWEEN ? AND ? 
Hibernate: 
    WITH query AS (select 
     ROW_NUMBER() OVER (
    order by 
     this_.lastuse asc) as __hibernate_row_nr__, 
     this_.combattemplateid as id89_0_, 
     this_1_.combattypeid as combatty2_89_0_, 
     this_1_.combattargetid as combatta3_89_0_, 
     this_1_.resourcenameid as resource4_89_0_, 
     this_1_.resourcedescriptionid as resource5_89_0_, 
     this_1_.rewardloottemplateid as rewardlo6_89_0_, 
     this_1_.combatcontainertypeid as combatco7_89_0_, 
     this_.requirementtemplateid as requirem2_90_0_, 
     this_.assetid as assetid90_0_, 
     this_.durationinsec as duration4_90_0_, 
     this_.lastuse as lastuse90_0_ 
    from 
     tournament_tournamenttemplate this_ 
    inner join 
     readyforcombat_combattemplate this_1_ with (updlock, rowlock) 
      on this_.combattemplateid=this_1_.id) SELECT 
      * 
    FROM 
     query 
    WHERE 
     __hibernate_row_nr__ BETWEEN ? AND ? 
Hibernate: 
    update 
     Tournament_TournamentTemplate 
    set 
     RequirementTemplateId=?, 
     AssetId=?, 
     DurationInSec=?, 
     LastUse=? 
    where 
     combatTemplateId=? 

回答

2

锁定表会工作得很好,如果每个操作需要经过这些步骤将没有成本(因为你需要反正东西锁)。

真的,你需要重新审视你的需求和体系结构,但它有一些非常快速的可怕杂乱的气味。只需要对随机化负责,在服务器上运行它,并在需要时随机化。保持简单。

+0

是的,我觉得需要一个企业搜索解决方案(这是一个结果相关性问题)。我会用[Solr](https://lucene.apache.org/solr/)和一个自定义的[QueryElevationComponent](https://wiki.apache.org/solr/QueryElevationComponent)来解决这个问题。 –

+0

“只需对随机化负责,在服务器上运行它,在需要时随机化。”我想这样做,但是服务器是相同的,所以这意味着他们都会尝试在某个时间点随机化,而其中只有一个应该。 – Nepoxx

+1

他们是你的服务器,有一个负责这个过程(总是可以在某个数据库表中使用一个条目)。只要知道如果该服务器停机但处理故障转移情况。 –

0

这是一个有趣的谜题。由于我不知道如何偶尔确定,它是存储过程的一个参数。但是,决策逻辑应该在数据库中实现。这要比跨多台服务器协调决策要简单得多。如果你可以更具体,我可以改进我的答案。

这个想法是将五个最近最少使用的行存储在一个带有计算序数的表变量中。计算@pick作为0到4之间的随机数并更新myTable中计算出的序号被选中的行。

CREATE PROCEDURE [dbo].[GetPermutedMostRecentlyUsed] 
AS 
BEGIN 

    DECLARE @isTimeToPermute BIT 
    SELECT TOP 1 @isTimeToPermute=CASE WHEN Expiry<GETDATE() THEN 1 ELSE 0 END FROM MyTable ORDER BY LastUsedDate DESC 

    IF @isTimeToPermute = 1 
    BEGIN 
     BEGIN TRAN 
      SELECT @isTimeToPermute=CASE WHEN Expiry<GETDATE() THEN 1 ELSE 0 END FROM MyTable WITH (TABLOCKX) ORDER BY LastUsedDate DESC 
      IF @isTimeToPermute = 1 
      BEGIN 
       DECLARE @P TABLE 
       (
        ID INT PRIMARY KEY NOT NULL, 
        Ordinal INT NOT NULL 
       ) 

       INSERT @P (ID, Ordinal) 
       SELECT TOP 5 ID, ROW_NUMBER() OVER(ORDER BY LastUsedDate ASC) - 1 AS Ordinal 
       FROM MyTable WITH (TABLOCKX) 

       DECLARE @Pick INT; SET @Pick = FLOOR(RAND() * 5) 

       UPDATE MyTable SET LastUsedDate=GETDATE(), Expiry=DATEADD(SECOND, 300, GETDATE()) 
       FROM MyTable AS T 
       INNER JOIN @P AS P ON P.ID=T.ID AND [email protected] 
      END 
     COMMIT TRAN 
    END 

    SELECT TOP 1 * FROM MyTable ORDER BY LastUsedDate DESC 

END 
GO 

只需调用它没有任何参数。

EXEC GetPermutedMostRecentlyUsed 

您将不得不调整列名称等以匹配您的表模式。

编辑为在内部计算@isTimeToPermute并执行表锁定。只有在需要对记录进行排列时才会执行表锁。

+0

这绝对是一个有趣的方法。确定何时选择新行的时间在行本身中定义。每行都有一个“过期时间”,这是一行可以作为活动行的时间限制。因此,例如,如果某行的expirationTime为5分钟,如果该行被随机挑选为活动行,则该行应在5分钟内“过期”,并应在最近最少使用的5个中选择新的活动行行。 – Nepoxx

相关问题