总分类帐(Django Atomic操作)



我想知道如何处理一般分类帐。考虑这样的架构:

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

但是最后一个请求已经过时了,因此余额现在更高。

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

更新:

按照一些答案,我使用Select进行更新提出了此解决方案:

    with transaction.atomic():
        new_entries = prepare_entries()
        for new_entry in new_entries:
            new_entry.save()

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

您可以使用select_for_update(返回将锁定行直到交易结束的QuerySet):

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表达式:

an f()对象表示模型字段的值。它做到了 可能参考模型字段值并执行数据库 使用它们的操作而无需实际将其拉出 数据库进入Python内存。

例如:

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

假设您的数据库支持它(为此,它应该)将整个操作包裹在事务中。即以"开始交易"调用开始,然后以提交结尾。

这保证了整个交易是执行的,或者没有执行。您也可能需要在此操作时锁定桌子,以确保其他过程的外观一致。

确切的工作以及如何做,通常是数据库依赖性的,因为事务处理与行和桌锁之间的关系因数据库到数据库和引擎而异。

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

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

最新更新