Opened 13 years ago
Closed 13 years ago
#17439 closed Bug (fixed)
prefetch_related issues extra queries for optional OneToOneFields
Reported by: | Owned by: | Aymeric Augustin | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Ready for checkin | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Given these models:
class ModelA(models.Model): pass class ModelB(models.Model): a = models.OneToOneField(ModelA, related_name='b')
I expect that after doing ModelA.objects.prefetch_related('b')
, the b
attribute of all the results can be accessed without causing another query. This is not the case:
>>> from app.models import * >>> a = ModelA.objects.all()[0] SELECT "app_modela"."id" FROM "app_modela" LIMIT 1 >>> a.b # should do a query, prefetch_related was not used SELECT "app_modelb"."id", "app_modelb"."a_id" FROM "app_modelb" WHERE "app_modelb"."a_id" = 1 Traceback (most recent call last): File "<console>", line 1, in <module> File "/srv/python-environments/egg/src/django/django/db/models/fields/related.py", line 255, in __get__ rel_obj = self.get_query_set(instance=instance).get(**params) File "/srv/python-environments/egg/src/django/django/db/models/query.py", line 361, in get % self.model._meta.object_name) DoesNotExist: ModelB matching query does not exist. ## it did another query, and raised DoesNotExist, just like expected. >>> a = ModelA.objects.all().prefetch_related('b')[0] SELECT "app_modela"."id" FROM "app_modela" LIMIT 1 SELECT "app_modelb"."id", "app_modelb"."a_id" FROM "app_modelb" >>> a.b # should not do a query, we already got all the `b`s SELECT "app_modelb"."id", "app_modelb"."a_id" FROM "app_modelb" WHERE "app_modelb"."a_id" = 1 Traceback (most recent call last): File "<console>", line 1, in <module> File "/srv/python-environments/egg/src/django/django/db/models/fields/related.py", line 255, in __get__ rel_obj = self.get_query_set(instance=instance).get(**params) File "/srv/python-environments/egg/src/django/django/db/models/query.py", line 361, in get % self.model._meta.object_name) DoesNotExist: ModelB matching query does not exist. ## Another query was issued even though we know it can not return any results >>>
Any possible b
object was already fetched by prefetch_related
, but
accessing the property causes another query anyway. Note that this is only the
case when a
does not have a b
object, the extra query does not happen if
there was a b
fetched by prefetch_related
.
Attachments (2)
Change History (15)
by , 13 years ago
Attachment: | attribute_cache_test.diff added |
---|
comment:1 by , 13 years ago
comment:2 by , 13 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:3 by , 13 years ago
The same problem also exists when traversing the relation in the reverse direction.
comment:5 by , 13 years ago
I think #10227 will need to be fixed first, but that probably won't fix this bug without some additional work in the prefetch code.
comment:6 by , 13 years ago
Agreed. #13839 is also in the same area (ie. caching the absence of a related object).
comment:8 by , 13 years ago
This is easier to fix after r17890. I think the attached patch does the job. Luke, I'd appreciate a sanity check if you have some time.
by , 13 years ago
Attachment: | 17439.patch added |
---|
comment:9 by , 13 years ago
Owner: | changed from | to
---|
I'm assigning the ticket to myself in order to keep track of it. Feel free to commit the patch if you like it.
comment:11 by , 13 years ago
Triage Stage: | Accepted → Ready for checkin |
---|
comment:12 by , 13 years ago
In fact, since r17890, prefetch_related can die with an exception on missing reverse-related objects -- it relied on a bug.
It looks like this is part of a bigger problem -- DoesNotExist is never cached:
According to https://docs.djangoproject.com/en/1.2/topics/db/optimization/#understand-cached-attributes, the second time accessing an attribute should not cause a query. I've added a patch with a failing test for this.