#33835 closed New feature (wontfix)
Select_related().only() in the Prefetch() should automatically add primary keys for reverse relations.
Reported by: | Ipakeev | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Simon Charette | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
I'm trying to optimize graphQL queries. When I use Prefetch with select_related and only optimizations, I encounter an increase in database queries. This is because I'm prefetching Book via InverseManyToOne relation and It needs a publisher_id in the only method.
class Author(models.Model): name = models.CharField(max_length=32) class Book(models.Model): title = models.CharField(max_length=32) author = models.ForeignKey("Author", on_delete=models.CASCADE) publisher = models.ForeignKey( "Publisher", on_delete=models.CASCADE, related_name="books", ) class Publisher(models.Model): address = models.CharField(max_length=32) class AuthorType(DjangoObjectType): class Meta: model = Author class BookType(DjangoObjectType): class Meta: model = Book class PublisherType(DjangoObjectType): class Meta: model = Publisher
Fill in the database:
with transaction.atomic(): authors = Author.objects.bulk_create([ Author(name=f"Name{i}") for i in range(10) ]) publishers = Publisher.objects.bulk_create([ Publisher(address=f"Address{i}") for i in range(10) ]) Book.objects.bulk_create([ Book( title=f"Title{i}", author=random.choice(authors), publisher=random.choice(publishers), ) for i in range(20) ])
GraphQL query:
{ publishers { address books { title author { name } } } }
22 db queries:
class Query(graphene.ObjectType): publishers = graphene.List(PublisherType) def resolve_publishers(self, info): queryset = Book.objects.select_related("author").only("title", "author__name") return Publisher.objects.prefetch_related(Prefetch("books", queryset)).only("address")
2 db queries:
class Query(graphene.ObjectType): publishers = graphene.List(PublisherType) def resolve_publishers(self, info): queryset = Book.objects.select_related("author").only("title", "author__name", "publisher_id") return Publisher.objects.prefetch_related(Prefetch("books", queryset)).only("address")
I fixed function prefetch_related_objects at django/db/models/query.py and now I don't need publisher_id in only method:
... prefetcher, descriptor, attr_found, is_fetched = get_prefetcher( first_obj, through_attr, to_attr ) from django.db.models.fields.related_descriptors import ReverseManyToOneDescriptor if type(descriptor) is ReverseManyToOneDescriptor: lookup.queryset.query.deferred_loading[0].add(descriptor.field.attname) if not attr_found: raise AttributeError( ...
Attachments (2)
Change History (4)
by , 2 years ago
by , 2 years ago
comment:1 by , 2 years ago
Cc: | added |
---|---|
Resolution: | → wontfix |
Status: | new → closed |
Summary: | Select_related().only() in the Prefetch class increases the number of queries for the InverseManyToOne relation. → Select_related().only() in the Prefetch() should automatically add primary keys for reverse relations. |
Type: | Bug → New feature |
comment:2 by , 2 years ago
I agree with Mariusz's conclusion. If you're looking for a solution to catch these problems early through in development on in your test suite without peppering with it assertNumQueries
I suggest you look into third-party solutions that will emit warnings when such problems occur.
In your particular case you would have gotten a warning along the lines of
UnsealedAttributeAccess: Attempt to fetch deferred field "publisher_id" on sealed <Book instance>
Which can be elevated to an error during development or tests via warnings.filterwarnings('error', category=UnsealedAttributeAccess)
This origin of this package was the exact same as yours; we needed a tool to allow the efficient creation of Queryset for a GraphQL querying endpoint and wanted to catch N+1 query problems due to missing select_related
, prefetch_related
or too eager usage of field deferral.
Thanks for this proposition, however,
prefetch_related_objects()
shouldn't implicitly add any columns, this could be unexpected and misleading. Users who useonly()
/defer()
are fully responsible for providing a proper set of columns, see a note in docs: