考虑这个简单的例子:
# a bank account class
class Account:
@transaction.commit_on_success
def withdraw(self, amount):
# code to withdraw money from the account
@transaction.commit_on_success
def add(self, amount):
# code to add money to the account
# somewhere else
@transaction.commit_on_success
def makeMoneyTransaction(src_account, dst_account, amount):
src_account.withdraw(amount)
dst_account.add(amount)
(摘自https://code.djangoproject.com/ticket/2227)
如果Account.add()
中出现异常,Account.withdraw()
中的事务仍将被提交,资金将丢失,因为Django目前不处理嵌套事务。
如果不对Django应用补丁,我们如何确保将提交发送到数据库,但只有当@transaction.commit_on_success
装饰器下的主函数完成而没有引发异常时才会发送?
我遇到了这个片段:http://djangosnippets.org/snippets/1343/,似乎它可以完成这项工作。如果我使用它,我应该注意哪些缺点?
如果你能提供帮助,请提前感谢。
P.S。为了提高可见性,我正在复制之前引用的代码段:
def nested_commit_on_success(func):
"""Like commit_on_success, but doesn't commit existing transactions.
This decorator is used to run a function within the scope of a
database transaction, committing the transaction on success and
rolling it back if an exception occurs.
Unlike the standard transaction.commit_on_success decorator, this
version first checks whether a transaction is already active. If so
then it doesn't perform any commits or rollbacks, leaving that up to
whoever is managing the active transaction.
"""
commit_on_success = transaction.commit_on_success(func)
def _nested_commit_on_success(*args, **kwds):
if transaction.is_managed():
return func(*args,**kwds)
else:
return commit_on_success(*args,**kwds)
return transaction.wraps(func)(_nested_commit_on_success)
答案 0 :(得分:5)
此代码段的问题在于,它不能让您在不回滚外部事务的情况下回滚内部事务。例如:
@nested_commit_on_success
def inner():
# [do stuff in the DB]
@nested_commit_on_success
def outer():
# [do stuff in the DB]
try:
inner()
except:
# this did not work, but we want to handle the error and
# do something else instead:
# [do stuff in the DB]
outer()
在上面的示例中,即使inner()
引发异常,也不会回滚其内容。
您需要的是内部“交易”的savepoint。对于您的代码,它可能如下所示:
# a bank account class
class Account:
def withdraw(self, amount):
sid = transaction.savepoint()
try:
# code to withdraw money from the account
except:
transaction.savepoint_rollback(sid)
raise
def add(self, amount):
sid = transaction.savepoint()
try:
# code to add money to the account
except:
transaction.savepoint_rollback(sid)
raise
# somewhere else
@transaction.commit_on_success
def makeMoneyTransaction(src_account, dst_account, amount):
src_account.withdraw(amount)
dst_account.add(amount)
从Django 1.6开始,atomic()装饰器就是这样做的:它使用一个事务用于外部使用装饰器,任何内部使用都使用一个保存点。
答案 1 :(得分:2)
Django 1.6引入了@atomic,它完全符合我的要求!
它不仅支持“嵌套”事务,而且还取代了旧的,功能较弱的装饰器。对于跨不同Django应用程序的事务管理,有一个独特且一致的行为是很好的。