Django的select_for_update不能在事务之外使用。

20

我曾使用的是 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个回答

33

1
如果一个工作有一个外键,那么相关的表也会被锁定吗? - Olivier Pons
1
通常在数据库更新时,会锁定特定的表/行,而不是相关的行。 - Kingpin2k
使用多个数据库时,请确保通过transaction.atomic()函数的using参数在正确的数据库上开启事务。 - astifter

11

附加说明

从Django 2.0版本开始,默认情况下相关行被锁定(不确定之前的行为),可以使用与select_related相同的方式使用of参数来指定要锁定的行:

默认情况下,select_for_update()锁定查询选择的所有行。例如,除了查询集模型的行外,还会锁定select_related()中指定的相关对象的行。如果不需要此行为,请使用与select_related()相同的语法在select_for_update(of=(...))中指定要锁定的相关对象。使用'value' self '来引用查询集的模型。

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


3

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来设置数据库设置:

'ATOMIC_REQUESTS': True

# "settings.py"

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

-1

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

2
这会改变整个数据库的行为,而不仅仅是某个特定的代码片段。 - mrroot5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接