Ticket #2705: for_update_9962.diff

File for_update_9962.diff, 12.6 KB (added by anih, 6 years ago)

for update patch with transaction.commit_unless_managed() inside select_for_update()

  • django/db/models/sql/query.py

     
    1313from django.utils.datastructures import SortedDict
    1414from django.utils.encoding import force_unicode
    1515from django.db.backends.util import truncate_name
    16 from django.db import connection
     16from django.db import connection, DatabaseError
    1717from django.db.models import signals
    1818from django.db.models.fields import FieldDoesNotExist
    1919from django.db.models.query_utils import select_related_descend
     
    2929except NameError:
    3030    from sets import Set as set     # Python 2.3 fallback
    3131
    32 __all__ = ['Query', 'BaseQuery']
     32__all__ = ['Query', 'BaseQuery', 'LockNotAvailable']
    3333
     34class LockNotAvailable(DatabaseError):
     35    '''
     36    Raised when a query fails because a lock was not available.
     37    '''
     38    pass
     39
    3440class BaseQuery(object):
    3541    """
    3642    A single SQL query.
     
    7379        self.order_by = []
    7480        self.low_mark, self.high_mark = 0, None  # Used for offset/limit
    7581        self.distinct = False
     82        self.select_for_update = False
     83        self.select_for_update_nowait = False
    7684        self.select_related = False
    7785        self.related_select_cols = []
    7886
     
    187195        obj.order_by = self.order_by[:]
    188196        obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
    189197        obj.distinct = self.distinct
     198        obj.select_for_update = self.select_for_update
     199        obj.select_for_update_nowait = self.select_for_update_nowait
    190200        obj.select_related = self.select_related
    191201        obj.related_select_cols = []
    192202        obj.aggregates = self.aggregates.copy()
     
    307317
    308318        query.clear_ordering(True)
    309319        query.clear_limits()
     320        query.select_for_update = False
    310321        query.select_related = False
    311322        query.related_select_cols = []
    312323        query.related_select_fields = []
     
    425436                        result.append('LIMIT %d' % val)
    426437                result.append('OFFSET %d' % self.low_mark)
    427438
     439        if self.select_for_update and self.connection.features.has_select_for_update:
     440            nowait = self.select_for_update_nowait and self.connection.features.has_select_for_update
     441            result.append("%s" % self.connection.ops.for_update_sql(nowait=nowait))
     442
    428443        params.extend(self.extra_params)
    429444        return ' '.join(result), tuple(params)
    430445
     
    20282043            else:
    20292044                return
    20302045        cursor = self.connection.cursor()
    2031         cursor.execute(sql, params)
     2046        try:
     2047            cursor.execute(sql, params)
     2048        except DatabaseError, e:
     2049            if self.connection.features.has_select_for_update_nowait and self.connection.ops.signals_lock_not_available(e):
     2050                raise LockNotAvailable(*e.args)
     2051            raise
    20322052
    20332053        if not result_type:
    20342054            return cursor
  • django/db/models/manager.py

     
    125125    def order_by(self, *args, **kwargs):
    126126        return self.get_query_set().order_by(*args, **kwargs)
    127127
     128    def select_for_update(self, *args, **kwargs):
     129        return self.get_query_set().select_for_update(*args, **kwargs)
     130       
    128131    def select_related(self, *args, **kwargs):
    129132        return self.get_query_set().select_related(*args, **kwargs)
    130133
  • django/db/models/__init__.py

     
    1111from django.db.models.fields.subclassing import SubfieldBase
    1212from django.db.models.fields.files import FileField, ImageField
    1313from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel
     14from django.db.models.sql.query import LockNotAvailable
    1415from django.db.models import signals
    1516
    1617# Admin stages.
  • django/db/models/query.py

     
    418418        del_query = self._clone()
    419419
    420420        # Disable non-supported fields.
     421        del_query.query.select_for_update = False
    421422        del_query.query.select_related = False
    422423        del_query.query.clear_ordering()
    423424
     
    557558        else:
    558559            return self._filter_or_exclude(None, **filter_obj)
    559560
     561    def select_for_update(self, **kwargs):
     562        """
     563        Returns a new QuerySet instance that will select objects with a
     564        FOR UPDATE lock.
     565        """
     566        # Default to false for nowait
     567        nowait = kwargs.pop('nowait', False)
     568        obj = self._clone()
     569        obj.query.select_for_update = True
     570        obj.query.select_for_update_nowait = nowait
     571        transaction.commit_unless_managed()
     572        return obj
     573
    560574    def select_related(self, *fields, **kwargs):
    561575        """
    562576        Returns a new QuerySet instance that will select related objects.
  • django/db/backends/mysql/base.py

     
    2222    raise ImproperlyConfigured("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__)
    2323
    2424from MySQLdb.converters import conversions
    25 from MySQLdb.constants import FIELD_TYPE, FLAG
     25from MySQLdb.constants import FIELD_TYPE, FLAG, ER
    2626
    2727from django.db.backends import *
    2828from django.db.backends.mysql.client import DatabaseClient
     
    112112    update_can_self_select = False
    113113    allows_group_by_pk = True
    114114    related_fields_match_type = True
     115    has_select_for_update = True
     116    has_select_for_update_nowait = False
    115117
    116118class DatabaseOperations(BaseDatabaseOperations):
    117119    def date_extract_sql(self, lookup_type, field_name):
     
    207209        # MySQL doesn't support microseconds
    208210        return unicode(value.replace(microsecond=0))
    209211
     212    signals_deadlock = lambda self, e: e.args[0] == ER.LOCK_DEADLOCK
     213
    210214    def year_lookup_bounds(self, value):
    211215        # Again, no microseconds
    212216        first = '%s-01-01 00:00:00'
  • django/db/backends/oracle/base.py

     
    3636    needs_datetime_string_cast = False
    3737    uses_custom_query_class = True
    3838    interprets_empty_strings_as_nulls = True
     39    has_select_for_update = True
     40    has_select_for_update_nowait = True
    3941
    4042
    4143class DatabaseOperations(BaseDatabaseOperations):
     
    201203                                       'column': column_name})
    202204        return output
    203205
     206    def signals_deadlock(self, exception):
     207        return exception.args[0].code == 60
     208
     209    def signals_lock_not_available(self, exception):
     210        return exception.args[0].code == 54
     211
    204212    def start_transaction_sql(self):
    205213        return ''
    206214
  • django/db/backends/__init__.py

     
    8181    # If True, don't use integer foreign keys referring to, e.g., positive
    8282    # integer primary keys.
    8383    related_fields_match_type = False
     84    has_select_for_update = False
     85    has_select_for_update_nowait = False
    8486
    8587class BaseDatabaseOperations(object):
    8688    """
     
    158160        """
    159161        return []
    160162
     163    def for_update_sql(self, nowait=False):
     164        """
     165        Return FOR UPDATE SQL clause to lock row for update
     166        """
     167        if nowait:
     168            nowaitstr = ' NOWAIT'
     169        else:
     170            nowaitstr = ''
     171        return 'FOR UPDATE' + nowaitstr
     172
    161173    def fulltext_search_sql(self, field_name):
    162174        """
    163175        Returns the SQL WHERE clause to use in order to perform a full-text
  • django/db/backends/postgresql_psycopg2/base.py

     
    1515try:
    1616    import psycopg2 as Database
    1717    import psycopg2.extensions
     18    from psycopg2 import errorcodes
    1819except ImportError, e:
    1920    from django.core.exceptions import ImproperlyConfigured
    2021    raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
     
    2930class DatabaseFeatures(BaseDatabaseFeatures):
    3031    needs_datetime_string_cast = False
    3132    uses_savepoints = True
     33    has_select_for_update = True
     34    has_select_for_update_nowait = True
    3235
    3336class DatabaseOperations(PostgresqlDatabaseOperations):
    3437    def last_executed_query(self, cursor, sql, params):
     
    3740        # http://www.initd.org/tracker/psycopg/wiki/psycopg2_documentation#postgresql-status-message-and-executed-query
    3841        return cursor.query
    3942
     43    signals_deadlock = lambda self, e: e.pgcode == errorcodes.DEADLOCK_DETECTED
     44
     45    signals_lock_not_available = lambda self, e: e.pgcode == errorcodes.LOCK_NOT_AVAILABLE
     46   
     47
    4048class DatabaseWrapper(BaseDatabaseWrapper):
    4149    operators = {
    4250        'exact': '= %s',
  • docs/ref/models/querysets.txt

     
    768768
    769769        Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    770770
     771``select_for_update(nowait=False)``
     772~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     773
     774Returns a queryset that will lock rows until the end of the transaction,
     775generating a SELECT ... FOR UPDATE statement on supported databases.
     776
     777For example::
     778
     779    entries = Entry.objects.select_for_update().filter(author=request.user)
     780
     781All matched entries will be locked until the end of the transaction block,
     782meaning that other transactions will be prevented from changing or acquiring
     783locks on them.
     784
     785Usually, if another transaction has already acquired a lock on one of the
     786selected rows, the query will block until the lock is released. If this is
     787not the behaviour you want, call ``select_for_update(nowait=True)``. This will
     788make the call non-blocking. If a conflicting lock is already acquired by
     789another transaction, ``django.db.models.LockNotAvailable`` will be raised when
     790the queryset is evaluated.
     791
     792Using blocking locks on a database can lead to deadlocks. This occurs when two
     793concurrent transactions are both waiting on a lock the other transaction
     794already holds. To deal with deadlocks, wrap your views that use
     795``select_for_update(nowait=False)`` with the
     796``django.views.decorators.deadlock.handle_deadlocks`` decorator.
     797
     798For example::
     799
     800    from django.db import transaction
     801    from django.views.decorators.deadlock import handle_deadlocks
     802
     803    @handle_deadlocks(max_retries=2)
     804    @transaction.commit_on_success
     805    def my_view(request):
     806        ...
     807
     808If the database engine detects a deadlock involving ``my_view`` and decides
     809to abort its transaction, it will be automatically retried. If deadlocks keep
     810occurring after two repeated attempts,
     811``django.views.decorators.DeadlockError`` will be raised, which can be
     812propagated to the user or handled in a middleware.
     813
     814Currently the ``postgresql_psycopg2``, ``oracle``, and ``mysql``
     815database backends support ``select_for_update()`` but MySQL has no
     816support for the ``nowait`` argument. Other backends will simply
     817generate queries as if ``select_for_update()`` had not been used.
     818
    771819QuerySet methods that do not return QuerySets
    772820---------------------------------------------
    773821
  • docs/ref/databases.txt

     
    276276column types have a maximum length restriction of 255 characters, regardless
    277277of whether ``unique=True`` is specified or not.
    278278
     279Row locking with ``QuerySet.select_for_update()``
     280-------------------------------------------------
     281
     282MySQL does not support the NOWAIT option to the SELECT ... FOR UPDATE
     283statement. However, you may call the ``select_for_update()`` method of a
     284queryset with ``nowait=True``. In that case, the argument will be silently
     285discarded and the generated query will block until the requested lock can be
     286acquired.
     287
    279288.. _sqlite-notes:
    280289
    281290SQLite notes
Back to Top