#35044: Accessing a deferred field clears reverse relations
-------------------------------------+-------------------------------------
               Reporter:  Adam       |          Owner:  nobody
  Johnson                            |
                   Type:             |         Status:  assigned
  Cleanup/optimization               |
              Component:  Database   |        Version:  dev
  layer (models, ORM)                |
               Severity:  Normal     |       Keywords:
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 `DeferredAttribute.__get__` calls `Model.refresh_from_db()` to load the
 deferred field. Whilst `refresh_from_db()` does load the intended field,
 it also clears a lot of cached data, including reverse relations. This can
 causes extra queries when those relations are accessed before and after a
 deferred field.

 For example, take these models:

 {{{
 class Book(models.Model):
     title = models.TextField()
     ...


 class BookText(models.Model):
     book = models.OneToOneField(Book, on_delete=models.CASCADE)
     text = models.TextField()
 }}}

 With query debug logging on, we can see that a second access to `booktext`
 after accessing the deferred `title` causes an unnecessary extra query:

 {{{
 In [1]: from example.models import *

 In [2]: b=Book.objects.defer('title').earliest('id')
 (0.000) SELECT "example_book"."id", "example_book"."author_id" FROM
 "example_book" ORDER BY "example_book"."id" ASC LIMIT 1; args=();
 alias=default

 In [3]: b.booktext
 (0.000) SELECT "example_booktext"."id", "example_booktext"."book_id",
 "example_booktext"."text" FROM "example_booktext" WHERE
 "example_booktext"."book_id" = 1 LIMIT 21; args=(1,); alias=default
 Out[3]: <BookText: BookText object (1)>

 In [4]: b.title
 (0.000) SELECT "example_book"."id", "example_book"."title" FROM
 "example_book" WHERE "example_book"."id" = 1 LIMIT 21; args=(1,);
 alias=default
 Out[4]: 'A 1'

 In [5]: b.booktext
 (0.000) SELECT "example_booktext"."id", "example_booktext"."book_id",
 "example_booktext"."text" FROM "example_booktext" WHERE
 "example_booktext"."book_id" = 1 LIMIT 21; args=(1,); alias=default
 Out[5]: <BookText: BookText object (1)>
 }}}

 This is due to `refresh_from_db()` clearing the reverse-related object
 cache.

 Spotted whilst working on #28586. My implementation for that will probably
 fix this issue, but I thought it best to report this separately.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/35044>
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 view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/0107018c72b01ed0-9c2a8373-e543-4b90-9dea-e8059ad27b70-000000%40eu-central-1.amazonses.com.

Reply via email to