我正在运行一个比赛网站,您尝试使用点击数字X赢得奖品。它是用Django编写的,用PostgreSQL在Heroku上运行。 每次单击都保存为Play模型的实例,该模型通过查看之前DB中的播放次数来计算其数量,并添加1.此数字保存在Play模型中。这是整个网站的核心,因为您播放的数字决定了您是否获得奖品。
最近,我们有一个案例,其中2人同时获得了中奖号码。检查数据库,我发现实际上有大约3%的游戏分享他们的数字。哎呀。 我已将“unique_together”添加到Play模型的“数字”和“游戏”字段中,因此数据库将帮助我避免将来重复使用数字,但我很担心未来的竞争条件可能会使系统跳过一些数字,如果有问题的数字在哪里获胜,那将是不好的。
我已经考虑锁定表格,但担心这可能会破坏网站的并发性(我们目前有多达500个并发播放器,并且期待未来更多)。
我应该采用什么策略100%确定我从未重复或跳过数字?
我的Play课程:
class Play(models.Model):
token = models.CharField(unique=True, max_length=200)
user = models.ForeignKey(User)
game = models.ForeignKey(Game)
datetime = models.DateTimeField(auto_now_add=True)
winner = models.BooleanField(default=False)
flagged = models.BooleanField(default=False)
number = models.IntegerField(blank=True, null=True)
ip = models.CharField(max_length=200, blank=True, null=True)
def assign_number(self, x=1000):
#try to assign number up to 1000 times, in case of race condition
while x > 0:
before = Play.objects.filter(id__lt=self.id, game=self.game)
try:
self.number = before.count()+1
self.save()
x=0
except:
x-=1
class Meta:
unique_together = (('user', 'game', 'datetime'), ('game','number'))
答案 0 :(得分:1)
一个简单的解决方案是将计数器和赢家用户放在游戏模型中。然后,您可以使用select_for_update
锁定记录:
game = Game.objects.select_for_update().get(pk=gamepk)
if game.number + 1 == X
# he is a winner
game.winner = request.user
game.number = game.number + 1
game.save()
else:
# u might need to stop the game if a winner already decided
作为同一交易的一部分,您还可以记录Player
个对象,这样您也可以知道是谁点击并跟踪其他信息,但不会将数字和获胜者放在那里。要使用select_for_update
,您需要使用postgresql_psycopg2
后端。
<强>更新强> 由于django默认设置为autocommit,因此必须将上述代码包装在原子事务中。来自django docs
选择更新 如果你依靠“自动事务”来提供select_for_update()和后续&gt;写操作之间的锁定 - 一个非常脆弱的设计,但仍然可能 - 你必须用atomic()包装相关的代码。
您可以使用@transaction.atomic
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()