2014-05-02 13 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

相關問題