不是 Django 中 @atomic() 的嵌套版本?
Not nesting version of @atomic() in Django?
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/
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/