Django的select_for_update不能在事务外使用

21 投票
4 回答
28239 浏览
提问于 2025-04-18 18:12

我之前在用Django 1.5.1,后来升级到了Django 1.6.6。

在Django 1.5.1里,我使用了“select for update”来确保操作是原子性的,也就是说要么全部成功,要么全部失败。

# "views.py"

from django.db import transaction

def some_method():    
    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.

有什么解决这个问题的方法吗?

4 个回答

-2

对我来说,即使使用了 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
    }
}
5

select_for_update() 必须在 一个事务 中运行。

所以,可以在视图中使用 @transaction.atomic,就像下面这样:

# "views.py"

from django.db import transaction

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

或者,在视图中使用 with transaction.atomic():,就像下面这样:

# "views.py"

from django.db import transaction

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

或者,在 settings.py数据库设置 中将 'ATOMIC_REQUESTS': True 设置为 True,就像下面这样:

# "settings.py"

DATABASES = {
    'default':{
        'ENGINE':'django.db.backends.postgresql',
        'NAME':'postgres',
        'USER':'postgres',
        'PASSWORD':'admin',
        'HOST':'localhost',
        'PORT':'5432',
        'ATOMIC_REQUESTS': True, # Here
    },
}
11

补充说明

从Django 2.0开始,相关的行默认是被锁定的(不太确定之前的行为是什么),而且可以通过和select_related一样的方式,使用of参数来指定要锁定的行:

默认情况下,select_for_update()会锁定查询中选择的所有行。例如,使用select_related()指定的相关对象的行也会被锁定,除了查询集模型的行。如果你不想这样,可以在select_for_update(of=(...))中指定你想要锁定的相关对象,使用和select_related()一样的字段语法。使用值'self'来指代查询集的模型。

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

33

答案就在错误信息里,把查询放在一个事务中。

Django的文档可以在这里找到:https://docs.djangoproject.com/en/dev/topics/db/transactions/#django.db.transaction.atomic

一种方法是:

# "views.py"

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:

撰写回答