不是 Django 中 @atomic() 的嵌套版本?

Not nesting version of @atomic() in Django?

来自docs of atomic()

atomic blocks can be nested

这听起来像是一个很棒的功能,但在我的用例中我想要相反的结果:我希望一旦用 @atomic() 装饰的块成功离开,交易就持久化。

有没有办法确保 django 事务处理的持久性?

背景

事务是 ACID。 "D" 代表耐用性。这就是为什么我认为事务不能在不丢失特性的情况下嵌套 "D".

示例:如果内部事务成功,但外部事务不成功,则外部和内部事务被回滚。结果:内部事务不持久。

我使用 PostgreSQL,但据我所知,这应该没什么大不了的。

你不能通过任何 API.

在保留所有 ACID 属性的情况下不能嵌套事务,并且并非所有数据库都支持嵌套事务。

只有最外层的原子块创建交易。内部原子块在事务内部创建保存点,并在退出内部块时释放或回滚保存点。因此,内部原子块提供原子性,但正如您所指出的,不是例如耐久度。

由于最外层的原子块创建一个事务,它必须提供原子性,如果包含的事务未提交,则不能将嵌套的原子块提交到数据库。

确保提交内部块的唯一方法是确保事务中的代码无误地完成执行。

我同意 knbk 的回答,这是不可能的:持久性仅存在于事务级别,而 atomic 提供了这一点。它不在保存点级别提供它。根据用例,可能会有解决方法。

我猜你的用例是这样的:

@atomic  # possibly implicit if ATOMIC_REQUESTS is enabled
def my_view():
    run_some_code()  # It's fine if this gets rolled back.
    charge_a_credit_card()  # It's not OK if this gets rolled back.
    run_some_more_code()  # This shouldn't roll back the credit card.

我想你会想要这样的东西:

@transaction.non_atomic_requests
def my_view():
    with atomic():
        run_some_code()
    with atomic():
        charge_a_credit_card()
    with atomic():
        run_some_more_code()

如果您的用例专门用于信用卡(就像我几年前遇到此问题时一样),我的同事发现 credit card processors actually provide mechanisms for handling this。类似的机制可能适用于您的用例,具体取决于问题结构:

@atomic
def my_view():
    run_some_code()
    result = charge_a_credit_card(capture=False)
    if result.successful:
        transaction.on_commit(lambda: result.capture())
    run_some_more_code()

另一种选择是使用非事务性持久性机制来记录您感兴趣的内容,例如日志数据库或要记录的内容的 Redis 队列。

由于 ACID,这种耐用性 不可能,只有一个连接。 (即嵌套块保持提交状态,而外部块回滚)这是 ACID 的结果,而不是 Django 的问题。想象一个超级数据库,table B 有一个指向 table A.

的外键
CREATE TABLE A (id serial primary key);
CREATE TABLE B (id serial primary key, b_id integer references A (id));
-- transaction
   INSERT INTO A DEFAULT VALUES RETURNING id AS new_a_id
   -- like it would be possible to create an inner transaction
      INSERT INTO B (a_id) VALUES (new_a_id)
   -- commit
-- rollback  (= integrity problem)

如果在(外部)事务回滚时内部 "transaction" 应该是持久的,那么完整性就会被破坏。回滚操作必须始终执行,这样它就永远不会失败,因此没有数据库会执行嵌套的独立事务。这样的选择性回滚是违反因果关系原则的,不能保证完整性。它也反对原子性。

交易与数据库连接有关。如果您创建 两个连接,则会创建两个独立的事务。一个连接看不到其他事务的未提交行(可以设置此隔离级别,但这取决于数据库后端)并且无法创建它们的外键并且数据库后端设计在回滚后保留完整性。

Django 支持多个数据库,因此支持多个连接。

# no ATOMIC_REQUESTS should be set for "other_db" in DATABASES

@transaction.atomic  # atomic for the database "default"
def my_view():
    with atomic():   # or set atomic() here, for the database "default"
        some_code()
        with atomic("other_db"):
            row = OtherModel.objects.using("other_db").create(**kwargs)
        raise DatabaseError

"other_db" 中的数据保持提交状态。

在 Django 中可能会创建一个有两个连接到同一个数据库的技巧,就像两个数据库一样,有一些数据库后端,但我确信它未经测试,它很容易出错,迁移问题,数据库后端的负载更大,必须在每个请求时创建真正的并行事务,并且无法优化。最好使用两个真实的数据库或者重新组织代码。

设置DATABASE_ROUTERS非常有用,但我不确定您是否对多个连接感兴趣。

即使这种确切的行为是不可能的,因为 django 3.2 有一个 durable=True[@transaction.atomic(durable=True)] 选项来确保这样的代码块不是嵌套的,所以偶然如果这样的代码是 运行,因为嵌套它会导致 RuntimeError 错误。 https://docs.djangoproject.com/en/dev/topics/db/transactions/#django.db.transaction.atomic 关于这个问题的文章https://seddonym.me/2020/11/19/trouble-atomic/