Django select_for_update 不能在事务之外使用

Posted

技术标签:

【中文标题】Django select_for_update 不能在事务之外使用【英文标题】:Django select_for_update cannot be used outside of a transaction 【发布时间】:2014-10-16 13:09:12 【问题描述】:

我使用的是 Django 1.5.1 并升级到 Django 1.6.6。

在 Django 1.5.1 中,我使用 select for update 来保证原子执行。

job_qs = Job.objects.select_for_update().filter(pk=job.id)
for job in job_qs:

不幸的是,现在这会引发错误:

  File "/srv/venvs/django-picdoc/local/lib/python2.7/site-packages/django/db/models/query.py", line 96, in __iter__
    self._fetch_all()

  File "/srv/venvs/django-picdoc/local/lib/python2.7/site-packages/django/db/models/query.py", line 857, in _fetch_all
    self._result_cache = list(self.iterator())

  File "/srv/venvs/django-picdoc/local/lib/python2.7/site-packages/django/db/models/query.py", line 220, in iterator
    for row in compiler.results_iter():

  File "/srv/venvs/django-picdoc/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 713, in results_iter
    for rows in self.execute_sql(MULTI):

  File "/srv/venvs/django-picdoc/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 776, in execute_sql
    sql, params = self.as_sql()

  File "/srv/venvs/django-picdoc/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 147, in as_sql
    raise TransactionManagementError("select_for_update cannot be used outside of a transaction.")

TransactionManagementError: select_for_update cannot be used outside of a transaction.

有什么解决方案可以解决这个问题?

【问题讨论】:

【参考方案1】:

答案在错误中,将查询包装在事务中

Django 的文档位于:https://docs.djangoproject.com/en/dev/topics/db/transactions/#django.db.transaction.atomic

一种方法是:

from django.db import transaction

def some_method():    
   with transaction.atomic():
      job_qs = Job.objects.select_for_update().filter(pk=job.id)
      for job in job_qs:

【讨论】:

如果Job有外键,那么相关表是否也被锁定了? 一般选择更新数据库锁定特定的表/行,而不是相关的行。【参考方案2】:

附录

从 Django 2.0 开始,默认情况下会锁定相关行(不确定之前的行为是什么),并且可以使用 of 参数以与 select_related 相同的样式指定要锁定的行:

默认情况下,select_for_update() 锁定查询选择的所有行。例如,除了查询集模型的行之外,select_related() 中指定的相关对象的行也被锁定。如果不需要,请使用与select_related() 相同的字段语法指定要锁定在select_for_update(of=(...)) 中的相关对象。使用值“self”来引用查询集的模型。

https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-for-update

【讨论】:

【参考方案3】:

对我来说,即使使用with transaction.atomic(): 也会发生这种情况。问题是我们没有在settings.py 文件中设置'ATOMIC_REQUESTS': True。现在这解决了问题。

如此处所述:https://docs.djangoproject.com/en/3.1/topics/db/transactions/

“在要启用此行为的每个数据库的配置中,将 ATOMIC_REQUESTS 设置为 True。”

所以我们在settings.py 中添加了:

DATABASES = 
    'default': 
        'ENGINE': 'django.db.backends.mysql',
        'NAME': os.environ['DB_NAME'],
        'USER': os.environ['DB_USER'],
        'PASSWORD': os.environ['DB_PASSWORD'],
        'HOST': os.environ['DB_HOST'],
        'PORT': '3306',
        'ATOMIC_REQUESTS': True
    

【讨论】:

这会改变所有数据库行为,而不仅仅是一段特定的代码。

以上是关于Django select_for_update 不能在事务之外使用的主要内容,如果未能解决你的问题,请参考以下文章

django select_for_update 获取关系锁

九Django的锁事务ajax

为啥不支持它的数据库可以简单地忽略 select_for_update?

从原子块调用的“select_for_update”仍然是 TransactionManagementError

Django的锁和事务

通过 select_for_update 记录中的两个 ForeignKey 优化访问