python - 增加计数器并在超过阈值时触发操作

标签 python django postgresql concurrency

我有一个这样的模型

class Thingy(models.Model):
    # ...
    failures_count = models.IntegerField()

我有需要执行此操作的并发进程(Celery 任务):

  1. 做一些处理
  2. 如果处理失败,则相应 Thingyfailures_counter 递增
  3. 如果 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)

最佳答案

据我所知,Django 的 ORM 不支持开箱即用 - 然而,这并不意味着它不能完成,你只需要深入到 SQL 级别(暴露,在 Django 的ORM 通过 Managerraw method ) 使其工作。

如果您使用的是 PostgresSQL >= 8.2,那么您可以使用 RETURNING在没有任何额外锁定的情况下获得 failure_count 的最终值(数据库仍将锁定,但仅足够长的时间来设置值,不会浪费与您通信的额外时间):

# ASSUMPTIONS: All IDs are valid and IDs are unique
# More defenses are necessary if either of these assumptions
# are not true.
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)

关于python - 增加计数器并在超过阈值时触发操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16335926/

相关文章:

javascript - 在 Django 中使用 'static' 模板导入 css

python - Django Queryset 使用 .extra 查询多对多关系

sql - 多对多关系——自动删除孤儿

ruby - Chef 独奏 : Installing build-essential recipe before postgresql

php - Symfony 中的 Postgres 部分唯一索引

python - 使用 Python 将 JSON 数据插入 SQL Server

python - 如何在 Ubuntu 16 中为 Open Edx 正确安装 Nfs

django - 如何修复 Django 错误 : "' unicode' object has no attribute 'tzinfo' "on database read

django - 无法连接 'str' 和 'tuple' 对象 - Django - johnny 缓存

Python 控制台 - 检查特定模块中定义的类或函数