总帐并发(django原子操作)

时间:2014-01-23 18:46:24

标签: python sql django concurrency

我想知道如何处理给定总帐的并发性。考虑这样的架构:

id   | account_id | credit | debit | balance |
1    | 123        | 0      | 100   | 200     |
2    | 456        | 100    | 0     | 100     |

要在分类帐中添加新条目,我会这样做(伪代码):

last_entry = get last account entry
is_credit = figure out if it is debit or credit entry
is_liability = figure out type of account

new_entry = Entry(
    foo='bar'
    # etc
)

if is_liability and is_credit
    new_entry.balance = last_entry.balance + amount

if is_liability and !is_credit
    new_entry.balance = last_entry.balance - amount

if !is_liability and is_credit
    new_entry.balance = last_entry.balance - amount

if !is_liability and !is_credit
    new_entry.balance = last_entry.balance + amount

new_entry.save()

我用这种方法看到的问题是:

假设有一个请求,我必须在分类帐中输入新条目。新条目将增加帐户余额。

如果在运行上述代码的过程中(假设在获得最后一个条目之后),会有另一个请求会再次增加余额。

所以余额会增加一次,其他请求会保存一个具有相同余额的新条目,因为它只会使用以下内容:

new_balance = last_entry.balance + amount

但是last_entry已经被另一个请求过时了,所以现在余额更高了。

任何想法如何确保这种情况不会发生(我知道这不太可能)。

更新:

根据一些答案,我使用SELECT FOR UPDATE:

提出了这个解决方案
    with transaction.atomic():
        new_entries = prepare_entries()
        for new_entry in new_entries:
            new_entry.save()

这是解决潜在并发问题的好方法吗?

3 个答案:

答案 0 :(得分:2)

您可以使用select_for_update(返回一个将锁定行直到事务结束的查询集):

with transaction.atomic(): # or commit_on_success/commit_manually in django < 1.6
    new_entries = prepare_entries()
    new_entries.select_for_update() # lock to update only in current transaction
    for new_entry in new_entries:
        #change new_entry somehow
        new_entry.save()

F表达式:

  

F()对象表示模型字段的值。它成功了   可以引用模型字段值并执行数据库   使用它们的操作,而不必实际将它们拉出来   数据库进入Python内存。

例如:

last_entry.update(balance=F('balance')+amount)

答案 1 :(得分:1)

假设您的数据库支持它(为此,它应该),将整个操作包装在一个事务中。即从“启动事务”调用开始,以提交结束。

这可以保证执行整个事务,或者不执行任何事务。您可能还需要在执行此操作时锁定表,以确保其他进程具有一致的外观。

您的具体操作以及操作方式通常取决于数据库,因为事务处理与行锁定和表锁定之间的关系因数据库和数据库以及引擎而异。

答案 2 :(得分:1)

计算应用于balance并使用update查询的总差异:

Model.objects.filter(pk=entry.pk).update(balance=F('balance') + difference)