【发布时间】:2023-04-10 01:27:01
【问题描述】:
我有一个这样的模型
class Thingy(models.Model):
# ...
failures_count = models.IntegerField()
我有需要执行此操作的并发进程(Celery 任务):
- 做一些处理
- 如果处理失败,则增加相应
failures_counter的Thingy - 如果
failures_counter超过某些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,请参阅下面的答案)。该问题询问了一种避免锁定的方法,这个答案是最佳的,因为它利用了multi-version concurrency control (MVCC) as implemented by PostgreSQL。
这个特定问题明确允许使用 PostgreSQL 功能,尽管许多 RDBMS 实现了UPDATE ... RETURNING,但它不是标准 SQL,并且 Django 的 ORM 不支持开箱即用,因此它需要通过 raw() 使用原始 SQL。相同的 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)
【问题讨论】:
-
最简单的方法是在 redis 中拥有两个计数器...
-
@armonge 这对于与我正在使用的设置不同的设置很有用。在我的设置中,我需要长期存储故障计数,而 Redis 仅用于缓存/同步。
标签: python django postgresql concurrency