#25496 closed Bug (fixed)
ModelChoiceField generates extra queries by not respecting prefetch_related
Reported by: | Matt d'Entremont | Owned by: | nobody |
---|---|---|---|
Component: | Forms | Version: | 1.8 |
Severity: | Release blocker | Keywords: | |
Cc: | marti@… | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Note: could be worked around if this were resolved: https://code.djangoproject.com/ticket/22841
When a ModelChoiceField
's choices are generated, the queryset's prefetch_related
calls are not evaluated, which can lead to 1 query per choice if the model's unicode
accesses a related field.
Example models:
from django.db import models class RelatedObj(models.Model): name = models.CharField(max_length=255) class ObjWithRelated(models.Model): name = models.CharField(max_length=255) related = models.ManyToManyField(RelatedObj) def __unicode__(self): return '{}: {}'.format(self.name, self.related.count())
With many models, we can see the following results:
from django.db import connection from django.forms import ModelChoiceField from django.test.utils import CaptureQueriesContext queryset = ObjWithRelated.objects.prefetch_related('related') field = ModelChoiceField(queryset) with CaptureQueriesContext(db.connection) as queries: list(field.choices) print 'ModelChoiceField query count: {}'.format(len(queries)) with CaptureQueriesContext(db.connection) as queries: [str(obj) for obj in queryset] print 'Regular query count: {}'.format(len(queries))
This will have the output of:
There are ways to work around this, but ideally there would be a solution which wouldn't require a workaround. We're using django-parler to translate models, without the prefetching we get 1 query per dropdown choice, per dropdown when a translated model is displayed in the django admin.
Change History (14)
comment:1 by , 9 years ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
comment:2 by , 9 years ago
Hello charettes,
Sorry, I used .count()
in the example to try and simplify things a little. In actuality we are accessing a field on a related object in our unicode
, in which case we definitely would want to use prefetch_related
to avoid 1 query per __unicode__
.
comment:3 by , 9 years ago
Resolution: | invalid → duplicate |
---|
Then I guess this is simply a duplicate of #22841?
comment:4 by , 9 years ago
Potentially?
My thinking was that #22841 is for passing in a queryset which will not be revaluated, where I want the re-evaluation to a avoid stale content, I just want the prefetch to be respected to avoid too many queries.
Also it took us a ton of investigation to determine why the prefetch wasn't performed, it is not obvious at all. If I pass a queryset along, I expect that it will be used entirely, not just certain pieces of it.
comment:5 by , 9 years ago
Resolution: | duplicate |
---|---|
Status: | closed → new |
Type: | Uncategorized → Bug |
Hmm it looks like it might be a regression in 1.8
cause by the fix for #23623 (fa534b92dda0771661a98a1ca302ced264d0a6da) which introduced the use of iterator()
to reduce memory usage.
From the prefetch_related
docs
Note that if you use
iterator()
to run the query,prefetch_related()
calls will be ignored since these two optimizations do not make sense together.
comment:6 by , 9 years ago
Has patch: | set |
---|---|
Severity: | Normal → Release blocker |
Triage Stage: | Unreviewed → Accepted |
Version: | → 1.8 |
comment:7 by , 9 years ago
Triage Stage: | Accepted → Ready for checkin |
---|
comment:11 by , 9 years ago
Cc: | added |
---|
It appears that this fix caused a regression. I have submitted pull requests with fixes, see ticket #25683.
Hi mdentremont,
I think you misunderstood how
prefetch_related
andcount()
interact. Thecount()
method never takes prefetched data into consideration and always result in a new query.If you want to avoid an extra query I suggest you
annotate()
your queryset with its count of related objects and adapt your models__unicode__
to lookup the annotated attribute first and fallback to callingself.related.count()
:Please use support channels before filling a ticket wiki:TicketClosingReasons/UseSupportChannels