在 Django 中回滚数据库连接失败的正确方法
Proper way to rollback on DB connection fail in Django
这更像是一个设计问题。
直到最近,我一直在我的开发环境中使用带有 SQLite 的 Django,但我现在已更改为生产环境中的 PostgreSQL。我的应用程序是使用 Heroku 部署的,几天后我意识到他们对数据库进行了随机维护,并且它在几分钟内就宕机了。
例如,有一个包含 3 个表的模型,一个 Procedure
每个表指向一个 ProcedureList
,而一个 ProcedureList
可以有多个 Procedure
. ProcedureUser
链接 ProcedureList
和用户,并为 ProcedureList
上的用户设置一些特定变量。最后有一个 ProcedureState
将 Procedure
与其特定用户的状态相关联。
在我的应用程序中,在其中一个视图中,我有一个按以下方式修改数据库的函数:
user = request.user
plist = ProcedureList.objects.get(id=idFromUrl)
procedures = Procedure.objects.filter(ProcedureList=pList)
pUser = ProcedureUser(plist, user, someVariables)
pUser.save()
for procedure in procedures:
pState = ProcedureState(plist, user, pUser, procedure, otherVariables)
pState.save()
所以我现在的想法是,如果 Heroku 决定在 object.save()
调用之间进行维护,我们就会遇到问题。稍后对 .save()
的调用将失败,数据库将被损坏。用户的请求当然会失败,也没有办法回滚之前的插入,因为无法与DB连接。
我的问题是,如果数据库出现故障(由 Heroku 维护、网络错误或其他原因引起),我们应该如何正确回滚数据库?要不要我们列个插入列表,等DB再上去回滚?
我正在使用 Python 3 和 Django 4,但我认为这是一个普遍问题,而不是特定于任何平台的问题。
in case of a DB fail (given by Heroku maintenance, network error or whatever), how are we supposed to correctly rollback the DB?
数据库通过atomic transactions [wiki]解决了这个问题。 atomic 事务是一组 all 或 none 提交的查询。因此,不可能对于此类事务,应用某些查询而其他查询不应用。
Django 提供了一个 transaction
context manager [Django-doc] 来在事务中执行工作:
from django.db import <strong>transaction</strong>
with <strong>transaction.atomic()</strong>:
user = request.user
plist = ProcedureList.objects.get(id=idFromUrl)
procedures = Procedure.objects.filter(ProcedureList=pList)
pUser = ProcedureUser(plist, user, someVariables)
pUser.save()
ProcedureState.objects.bulk_create([
ProcedureState(plist, user, pUser, procedure, otherVariables)
for procedure in procedures
])
在上下文块的末尾,它将提交 更改。这意味着如果数据库在此期间发生故障,将不会提交操作,并且该块将引发异常(通常是 IntegrityError
)。
Note: Django has a .bulk_create(…)
method [Django-doc] to create multiple items with a single database query, minimizing the bandwidth between the database and the application layer. This will usually outperform creating items in a loop.
这更像是一个设计问题。
直到最近,我一直在我的开发环境中使用带有 SQLite 的 Django,但我现在已更改为生产环境中的 PostgreSQL。我的应用程序是使用 Heroku 部署的,几天后我意识到他们对数据库进行了随机维护,并且它在几分钟内就宕机了。
例如,有一个包含 3 个表的模型,一个 Procedure
每个表指向一个 ProcedureList
,而一个 ProcedureList
可以有多个 Procedure
. ProcedureUser
链接 ProcedureList
和用户,并为 ProcedureList
上的用户设置一些特定变量。最后有一个 ProcedureState
将 Procedure
与其特定用户的状态相关联。
在我的应用程序中,在其中一个视图中,我有一个按以下方式修改数据库的函数:
user = request.user
plist = ProcedureList.objects.get(id=idFromUrl)
procedures = Procedure.objects.filter(ProcedureList=pList)
pUser = ProcedureUser(plist, user, someVariables)
pUser.save()
for procedure in procedures:
pState = ProcedureState(plist, user, pUser, procedure, otherVariables)
pState.save()
所以我现在的想法是,如果 Heroku 决定在 object.save()
调用之间进行维护,我们就会遇到问题。稍后对 .save()
的调用将失败,数据库将被损坏。用户的请求当然会失败,也没有办法回滚之前的插入,因为无法与DB连接。
我的问题是,如果数据库出现故障(由 Heroku 维护、网络错误或其他原因引起),我们应该如何正确回滚数据库?要不要我们列个插入列表,等DB再上去回滚?
我正在使用 Python 3 和 Django 4,但我认为这是一个普遍问题,而不是特定于任何平台的问题。
in case of a DB fail (given by Heroku maintenance, network error or whatever), how are we supposed to correctly rollback the DB?
数据库通过atomic transactions [wiki]解决了这个问题。 atomic 事务是一组 all 或 none 提交的查询。因此,不可能对于此类事务,应用某些查询而其他查询不应用。
Django 提供了一个 transaction
context manager [Django-doc] 来在事务中执行工作:
from django.db import <strong>transaction</strong>
with <strong>transaction.atomic()</strong>:
user = request.user
plist = ProcedureList.objects.get(id=idFromUrl)
procedures = Procedure.objects.filter(ProcedureList=pList)
pUser = ProcedureUser(plist, user, someVariables)
pUser.save()
ProcedureState.objects.bulk_create([
ProcedureState(plist, user, pUser, procedure, otherVariables)
for procedure in procedures
])
在上下文块的末尾,它将提交 更改。这意味着如果数据库在此期间发生故障,将不会提交操作,并且该块将引发异常(通常是 IntegrityError
)。
Note: Django has a
.bulk_create(…)
method [Django-doc] to create multiple items with a single database query, minimizing the bandwidth between the database and the application layer. This will usually outperform creating items in a loop.