[英]Preventing an action from being called multiple times by different servers
我有一个小表(<100行),其中包含一个有序的项目列表。 我想随机置换这些物品。 我想要这样做的方法是选择5个最近最少使用的项目并随机选择其中一个。 但是,我只想偶尔做一次。
我正在考虑使用存储过程执行此操作,然后查询简单地变为类似SELECT TOP 1 * FROM myTable ORDER BY LastUsedDate DESC
。
不幸的是,这个解决方案并不好。 如果每个排列之间的时间(每次运行存储过程)都是可变的,则每X分钟运行一次的SQL-Server作业将不起作用。 如果我让我的服务器执行排列,多个服务器可能最终会进行排列。
这是我想在服务器上做的逻辑:
但是,我可以想象锁定表并不是一个很好的解决方案。 所以我在寻找建议:)。
我在使用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();
}
但是Hibernate没有生成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=?
锁定表将正常工作,如果每个操作需要通过这些步骤将没有成本(因为你需要锁定一些东西)。
真的,你需要重新审视你的要求和架构,但这有一些东西的气味会很快变得非常混乱。 只需负责随机化,在服务器上运行它,在需要时随机化。 保持简单。
这是一个有趣的难题。 由于我不知道如何确定一次 ,它是存储过程的参数。 但是,决策逻辑应该在数据库中实现。 这比尝试跨多个服务器协调决策要简单得多。 如果你可以更具体,我可以改进我的答案。
这个想法是将五个最近最少使用的行存储在一个带有计算序数的表变量中。 将@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 P.Ordinal=@Pick
END
COMMIT TRAN
END
SELECT TOP 1 * FROM MyTable ORDER BY LastUsedDate DESC
END
GO
只要不加任何参数调用它。
EXEC GetPermutedMostRecentlyUsed
您必须调整列名等,以匹配您的表架构。
编辑在内部计算@isTimeToPermute
并执行表锁定。 只有在需要置换记录时才会进行表锁定。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.