#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 django-updates+unsubscr...@googlegroups.com.
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