All of these acquire a lock which conflicts with `KEY SHARE` locks. This
means that updates on tables which references a row locked by `FOR UPDATE`
will have to wait until the lock is released. This is to avoid any
unexpected changes on `PRIMARY KEY` fields but, **in Django, primary key
fields are read-only, so locking them makes no sense**.
There is no need to acquire these kind of locks, instead, we should (at
least be able to) use
- FOR NO KEY UPDATE
- FOR NO KEY UPDATE NOWAIT
- FOR NO KEY UPDATE SKIP LOCKED
which can be easily done by overriding BaseOperation.for_update_sql in
Postgres DatabaseOperation
{{{
def for_update_sql(self, nowait=False, skip_locked=False):
"""
Returns the FOR UPDATE SQL clause to lock rows for an update
operation.
"""
if nowait:
return 'FOR NO KEY UPDATE NOWAIT'
elif skip_locked:
return 'FOR NO KEY UPDATE SKIP LOCKED'
else:
return 'FOR NO KEY UPDATE'
}}}
Not doing so causes lock contention in our use case.
--
Ticket URL: <https://code.djangoproject.com/ticket/30375>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* version: 1.11 => 2.2
Old description:
New description:
Currently Django compiles select for update into one of
- FOR UPDATE
- FOR UPDATE NOWAIT
- FOR UPDATE SKIP LOCKED
All of these acquire a lock which conflicts with `KEY SHARE` locks. This
means that updates on tables which references a row locked by `FOR UPDATE`
will have to wait until the lock is released. This is to avoid any
unexpected changes on `PRIMARY KEY` fields but, **in Django, primary key
fields are read-only, so locking them makes no sense**.
There is no need to acquire these kind of locks, instead, we should (at
least be able to) use
- FOR NO KEY UPDATE
- FOR NO KEY UPDATE NOWAIT
- FOR NO KEY UPDATE SKIP LOCKED
which can be easily done by overriding BaseOperation.for_update_sql in
Postgres DatabaseOperation
For 1.11
{{{
def for_update_sql(self, nowait=False, skip_locked=False):
"""
Returns the FOR UPDATE SQL clause to lock rows for an update
operation.
"""
if nowait:
return 'FOR NO KEY UPDATE NOWAIT'
elif skip_locked:
return 'FOR NO KEY UPDATE SKIP LOCKED'
else:
return 'FOR NO KEY UPDATE'
}}}
For 2.2
{{{
def for_update_sql(self, nowait=False, skip_locked=False, of=()):
"""
Return the FOR UPDATE SQL clause to lock rows for an update operation.
"""
return 'FOR NO KEY UPDATE%s%s%s' % (
' OF %s' % ', '.join(of) if of else '',
' NOWAIT' if nowait else '',
' SKIP LOCKED' if skip_locked else '',
)
}}}
Not doing so causes lock contention in our use case.
--
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:1>
* version: 2.2 => master
Comment:
> ... in Django, primary key fields are read-only, so locking them makes
no sense
That's not true AFAIK. Even if `AutoField` are backed by sequences and
generated on the database side they can still be overridden by developers.
Same thing with non-`AutoField` primary keys (e.g.
`UUIDField(default=uuid.uuid4)`).
On the other hand updating a primary key while holding a lock is really
uncommon so I wouldn't be against adding a `select_for_udate(primary_key)`
parameter that defaults to `False`. We just have to make sure that we
still allow these operations to be performed somehow.
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:2>
Comment (by Manuel Weitzman):
Replying to [comment:2 Simon Charette]:
> > ... in Django, primary key fields are read-only, so locking them makes
no sense
>
> That's not true AFAIK. Even if `AutoField` are backed by sequences and
generated on the database side they can still be overridden by developers.
Same thing with non-`AutoField` primary keys (e.g.
`UUIDField(default=uuid.uuid4)`).
Then the docs are wrong or at least misleading: ''"The primary key field
is read-only. If you change the value of the primary key on an existing
object and then save it, a new object will be created alongside the old
one."" (https://docs.djangoproject.com/en/2.2/topics/db/models/)
Which means that UPDATEs over PKs are transformed into INSERTs
> On the other hand updating a primary key while holding a lock is really
uncommon so I wouldn't be against adding a `select_for_udate(primary_key)`
parameter that defaults to `False`. We just have to make sure that we
still allow these operations to be performed somehow.
That is possible, I tested today and only a few changes are required to
achieve this behavior. Maybe I could send a PR soon for review?
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:3>
Comment (by Simon Charette):
The docs are correct for `save` but primary keys are still updatable
through `queryset.filter(pk=old_value).update(pk=new_value)`.
Feel free to submit a PR. It'd be great to investigate if other backends
have a similar option as well.
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:4>
* stage: Unreviewed => Accepted
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:5>
* status: new => assigned
* needs_better_patch: 0 => 1
* owner: nobody => Manuel Weitzman
* needs_docs: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:6>
* needs_better_patch: 1 => 0
* needs_docs: 1 => 0
Comment:
This needs to be in the review queue :)
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:7>
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:8>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"a4e6030904df63b3f10aa0729b86dc6942b0458e" a4e6030]:
{{{
#!CommitTicketReference repository=""
revision="a4e6030904df63b3f10aa0729b86dc6942b0458e"
Fixed #30375 -- Added FOR NO KEY UPDATE support to
QuerySet.select_for_update() on PostgreSQL.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/30375#comment:9>