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

gue*_*tli 15 python django postgresql transactions acid

来自原子()文档

原子块可以嵌套

这听起来像一个很棒的功能,但在我的用例中我想要相反:我希望只要装饰的块@atomic()成功保留,事务就会持久.

有没有办法确保django的交易处理的持久性?

背景

交易是ACID."D"代表耐久性.这就是为什么我认为交易不能在不丢失特征"D"的情况下嵌套.

示例:如果内部事务成功,但外部事务不成功,则外部事务和内部事务将回滚.结果:内部事务不耐用.

我使用PostgreSQL,但AFAIK这应该不重要.

knb*_*nbk 7

你不能通过任何API来做到这一点.

保留所有ACID属性时不能嵌套事务,并且并非所有数据库都支持嵌套事务.

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

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

确保提交内部块的唯一方法是确保事务中的代码完成执行而没有任何错误.


Rec*_*nic 6

我同意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.
Run Code Online (Sandbox Code Playgroud)

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

@transaction.non_atomic_requests
def my_view():
    with atomic():
        run_some_code()
    with atomic():
        charge_a_credit_card()
    with atomic():
        run_some_more_code()
Run Code Online (Sandbox Code Playgroud)

如果你的使用情况,使用信用卡专(像我是当我在几年前就有这种问题),我的同事发现,信用卡处理器实际上提供了处理这种机制.类似的机制可能适用于您的用例,具体取决于问题结构:

@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()
Run Code Online (Sandbox Code Playgroud)

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

  • 您指向“信用卡处理器实际上提供处理此问题的机制”的链接看起来像两阶段提交协议:https://en.wikipedia.org/wiki/Two-phase_commit_protocol (2认同)

hyn*_*cer 6

由于ACID,只有一个连接,这种类型的耐久性不可能的.(即,当外部块被回滚时,嵌套块保持提交)这是ACID的结果,而不是Django的问题.想象一下一个超级数据库以及该表B具有表的外键的情况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)
Run Code Online (Sandbox Code Playgroud)

如果内部"事务"应该是持久的,而(外部)事务被回滚,则完整性将被破坏.必须始终实现回滚操作,以使其永远不会失败,因此没有数据库会实现嵌套的独立事务.这种选择性回滚是违反因果关系原则的,并且不能保证完整性.它也违反了原子性.

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

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


lor*_*vcs 2

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