#29786: Add option to lock rows with select_for_update() immediately
-------------------------------------+-------------------------------------
Reporter: ovalseven8 | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: 2.1
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Old description:
> In my web application I usually follow the "fat models, lightweight
> views" philosophy.
>
> To put it in a nutshell, I have two models in my app called "Event" and
> "Registration (for Event)":
>
> {{{
> class Event(models.Model):
> capacity = models.PositiveSmallIntegerField()
>
> def get_number_of_registered_tickets():
> return
> EventRegistration.objects.filter(event__exact=self).aggregate(total=Coalesce(Sum('number_tickets'),
> 0))['total']
>
> def reserve_tickets(self, number_tickets):
> Event.objects.filter(id=self.id).select_for_update()
> if self.get_number_of_registered_tickets() + number_tickets <=
> self.capacity:
> # create EventRegistration
> else:
> # handle error
>
> class EventRegistration(models.Model):
> time = models.DateTimeField(auto_now_add=True)
> event = models.ForeignKey(Event, on_delete=models.CASCADE)
> number_tickets =
> models.PositiveSmallIntegerField(validators=[MinValueValidator(1)])
> }}}
>
> Now, let's say for a specific event only one ticket is left and two users
> want to buy that ticket concurrently. Under unfortunate circumstances
> (when I do not use locking), it would be possible that both get the
> ticket and now I have a problem. So, I need to make sure that the
> registration for an event happens in sequence. That's the reason why I
> use **select_for_update()** in the method `reserve_tickets()` above.
>
> Unfortunately, the row is only locked when the QuerySet is evaluates
> what's not the case here. So, I need more or less a "dirty hack" like
> printing the queryset or creating a list etc.
>
> While this works, I do not think it's a nice solution. Especially for
> locking rows, I think it would be a good think if Django had a
> "lock/evaluate immediately" option.
>
> Different ideas how the solution API could look like:
> `select_for_update().evaluate()`
> `select_for_update(evaluate_immediately=True)`
>
> Of course, there're more possibilities. I think it would be a nice little
> feature and lazy evaluation is something you perhaps do not want for
> locking.
>
> So my question is if you're open for that, if yes I could look into it.
New description:
In my web application I usually follow the "fat models, lightweight views"
philosophy.
To put it in a nutshell, I have two models in my app called "Event" and
"Registration (for Event)":
{{{
class Event(models.Model):
capacity = models.PositiveSmallIntegerField()
def get_number_of_registered_tickets():
return
EventRegistration.objects.filter(event__exact=self).aggregate(total=Coalesce(Sum('number_tickets'),
0))['total']
@transaction.atomic
def reserve_tickets(self, number_tickets):
Event.objects.filter(id=self.id).select_for_update()
if self.get_number_of_registered_tickets() + number_tickets <=
self.capacity:
# create EventRegistration
else:
# handle error
class EventRegistration(models.Model):
time = models.DateTimeField(auto_now_add=True)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
number_tickets =
models.PositiveSmallIntegerField(validators=[MinValueValidator(1)])
}}}
Now, let's say for a specific event only one ticket is left and two users
want to buy that ticket concurrently. Under unfortunate circumstances
(when I do not use locking), it would be possible that both get the ticket
and now I have a problem. So, I need to make sure that the registration
for an event happens in sequence. That's the reason why I use
**select_for_update()** in the method `reserve_tickets()` above.
Unfortunately, the row is only locked when the QuerySet is evaluates
what's not the case here. So, I need more or less a "dirty hack" like
printing the queryset or creating a list etc.
While this works, I do not think it's a nice solution. Especially for
locking rows, I think it would be a good think if Django had a
"lock/evaluate immediately" option.
Different ideas how the solution API could look like:
`select_for_update().evaluate()`
`select_for_update(evaluate_immediately=True)`
Of course, there're more possibilities. I think it would be a nice little
feature and lazy evaluation is something you perhaps do not want for
locking.
So my question is if you're open for that, if yes I could look into it.
--
Comment (by ovalseven8):
Add decorator
--
Ticket URL: <https://code.djangoproject.com/ticket/29786#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/068.01d8ad3bb987c82f96eef870b02023e8%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.