【问题标题】:General Ledger Concurrency (django atomic operations)总账并发(django 原子操作)
【发布时间】:2014-01-23 18:46:24
【问题描述】:

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

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()

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

【问题讨论】:

    标签: python sql django concurrency


    【解决方案1】:

    您可以使用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)
    

    【讨论】:

    • 可以看看我的代码吗?我在我的问题中添加了一些示例代码。你觉得有什么问题吗?
    • 谢谢。我现在明白了。因此,一个打开的事务所做的更改对其他正在运行的事务是可见的。我更新了我的代码示例。现在简单多了。
    • 请注意,要使select_for_update 工作,您需要在事务中。
    • @kroolik 因为 django 1.6 这是默认行为,你总是在事务中,在这里查看有关自动提交的文档docs.djangoproject.com/en/dev/topics/db/transactions/…
    • 你不想自动提交select_for_update,因为提交会释放锁。
    【解决方案2】:

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

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

    具体做什么以及如何做通常取决于数据库,因为事务处理与行锁定和表锁定之间的关系因数据库和引擎而异。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 在这个调用中,我也可以原子地检查余额是否保持正数吗?还是我应该事后检查它并在它变为负数时回滚?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-21
      • 2019-03-26
      • 2014-01-11
      • 1970-01-01
      • 2022-01-10
      • 2019-12-17
      • 1970-01-01
      相关资源
      最近更新 更多