我有一个这样的模型
class Thingy(models.Model):
# ...
failures_count = models.IntegerField()
我有需要执行此操作的并发进程(芹菜任务):
- 做某种处理
failures_counter
如果处理失败相应的增量Thingy
- 如果
failures_counter
超过 some 的阈值Thingy
,则发出警告,但只有一个警告。
我有一些关于如何在没有竞争条件的情况下做到这一点的想法,例如使用显式锁(通过select_for_update
):
@transaction.commit_on_success
def report_failure(thingy_id):
current, = (Thingy.objects
.select_for_update()
.filter(id=thingy_id)
.values_list('failures_count'))[0]
if current == THRESHOLD:
issue_warning_for(thingy_id)
Thingy.objects.filter(id=thingy_id).update(
failures_count=F('failures_count') + 1
)
或者通过使用 Redis(它已经存在)进行同步:
@transaction.commit_on_success
def report_failure(thingy_id):
Thingy.objects.filter(id=thingy_id).update(
failures_count=F('failures_count') + 1
)
value = Thingy.objects.get(id=thingy_id).only('failures_count').failures_count
if value >= THRESHOLD:
if redis.incr('issued_warning_%s' % thingy_id) == 1:
issue_warning_for(thingy_id)
两种解决方案都使用锁。当我使用 PostgreSQL 时,有没有办法在不锁定的情况下实现这一点?
我正在编辑问题以包含答案(感谢 Sean Vieira,请参阅下面的答案)。这个问题询问了一种避免锁定的方法,这个答案是最佳的,因为它利用了 PostgreSQL 实现的多版本并发控制(MVCC)。
这个特定问题明确允许使用 PostgreSQL 功能,尽管许多 RDBMS 实现UPDATE ... RETURNING
了 ,但它不是标准 SQL,并且 Django 的 ORM 不支持开箱即用,因此它需要使用原始 SQL via raw()
。相同的 SQL 语句将在其他 RDBMS 中工作,但每个引擎都需要自己讨论同步、事务隔离和并发模型(例如,带有 MyISAM 的 MySQL 仍将使用锁)。
def report_failure(thingy_id):
with transaction.commit_on_success():
failure_count = Thingy.objects.raw("""
UPDATE Thingy
SET failure_count = failure_count + 1
WHERE id = %s
RETURNING failure_count;
""", [thingy_id])[0].failure_count
if failure_count == THRESHOLD:
issue_warning_for(thingy_id)