原子块可以嵌套
这听起来像是一个很棒的功能,但在我的用例中,我想要相反:我希望只要用@atomic()
修饰的块成功完成,事务就会持久。
有没有办法确保django交易处理的持久性?
交易是ACID。 “D”代表耐久性。这就是为什么我认为交易不能在不丢失特征“D”的情况下嵌套。
示例:如果内部事务成功,但外部事务不成功,则外部事务和内部事务将回滚。结果:内部交易不耐用。
我使用的是PostgreSQL,但AFAIK这应该不重要。
答案 0 :(得分:7)
您无法通过任何API执行此操作。
在保留所有ACID属性的同时,不能嵌套事务,也不是所有数据库都支持嵌套事务。
只有最外面的原子块才会创建一个事务。内部原子块在事务内部创建一个保存点,并在退出内部块时释放或回滚保存点。因此,内部原子块提供原子性,但正如您所指出的,不是例如耐久性。
由于最外面的原子块创建了一个事务,它必须提供原子性,如果没有提交包含的事务,你就不能将嵌套的原子块提交给数据库。
确保提交内部块的唯一方法是确保事务中的代码完成执行而没有任何错误。
答案 1 :(得分: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.
我想你想要的东西是:
@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队列。
答案 2 :(得分: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)
如果内部"交易"在(外部)事务被回滚时应该是持久的,然后完整性将被破坏。必须始终实现回滚操作,以使其永远不会失败,因此没有数据库会实现嵌套的独立事务。这种选择性回滚是违反因果关系原则的,并且不能保证完整性。它也违反了原子性。
该事务与数据库连接有关。如果您创建两个连接,则会创建两个独立的事务。一个连接没有看到未提交的其他事务行(可以设置此隔离级别,但它取决于数据库后端)并且不能创建它们的外键和完整性在数据库后端设计回滚后保留。
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非常有用,但我还不确定您是否对多个连接感兴趣。
答案 3 :(得分:1)
即使这种确切的行为是不可能的,因为 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/