In Django 6.1, when is a model instance in memory stale, and when do you still need refresh_from_db()?
answer
- an instance is a snapshot
- update() does not touch instances
- F() changed in Django 6.0
- fields, using, from_queryset
basics
~20 sAn instance holds the values read when it was loaded; QuerySet.update(), other processes and raw SQL change the row without touching it. refresh_from_db() reloads its fields. Since Django 6.0, F() assignments are refreshed automatically on save().
solid answer
~40 sA Django model instance is a snapshot: Django does not keep one shared object per row, so two queries for the same page give two independent instances, and nothing updates them when the row changes. After `WikiPage.objects.filter(pk=page.pk).update(...)`, after another request or task edits the row, or after raw SQL, the instance is stale and `page.refresh_from_db()` reloads its non-deferred fields and clears cached relations; `fields=[...]` narrows it and, since 5.1, `from_queryset=` lets you reload with `select_for_update()`. The classic F() case changed in 6.0: after `page.views = F("views") + 1; page.save()`, Django now refreshes the value itself (in the same query on SQLite, PostgreSQL and Oracle, lazily on MySQL/MariaDB). On 5.2 LTS the attribute still holds the expression, so a second `save()` increments again unless you refresh.
code
python · 17 linesfrom django.db import transaction
from django.db.models import F
page = WikiPage.objects.get(pk=7)
WikiPage.objects.filter(pk=page.pk).update(title="Installing on Linux")
page.title # still the old title: the instance is a snapshot
page.refresh_from_db(fields=["title"])
page.view_count = F("view_count") + 1
page.save(update_fields=["view_count"])
page.view_count # Django 6.x: the new integer, no manual refresh needed
with transaction.atomic():
page.refresh_from_db(from_queryset=WikiPage.objects.select_for_update())
page.body += "\n\nReviewed."
page.save(update_fields=["body"])go deeper
Recall that an instance does not change when the row is updated elsewhere, and that refresh_from_db() reloads it.
Explain which operations leave an instance stale, what refresh_from_db() reloads and clears, and how fields= narrows it.
Know the 6.0 F() change when reading older code, and reload with select_for_update() before read-modify-write on contended rows.
Keep instances short-lived in tasks and services, and prefer database-side updates over long-held objects on contended rows.
## An instance is a snapshot When Django loads a row, it builds a Python object holding the column values as they were **at that moment**. Django does not keep a registry of loaded objects: two queries for the same wiki page return two separate instances, and changing one does not change the other. Nothing pushes later database changes into an instance you already hold. So an instance becomes **stale** whenever the row changes by any route other than that instance's own `save()`: - `WikiPage.objects.filter(pk=page.pk).update(title="New")` changes the row but not `page`; - another request, a background task or a management command edits the same row; - a database trigger, a default or raw SQL changes a column; - another instance of the same row is saved in the same code path. ## refresh_from_db() `Model.refresh_from_db(using=None, fields=None, from_queryset=None)` (and `arefresh_from_db()` in async code) reloads the instance from the database: 1. All **non-deferred fields** are set to the values currently in the database, or only those named in `fields`. 2. **Cached relations are cleared**, so `page.space` is fetched again on next access. 3. Values that are not model fields, such as **annotations** and `@cached_property` attributes, are **not** reloaded. `from_queryset`, added in Django 5.1, lets you choose the queryset used for the reload: - `from_queryset=WikiPage.objects.select_for_update()` locks the row while reloading, inside a transaction; - `from_queryset=WikiPage.objects.select_related("space")` reloads and keeps a relation cached; - a custom manager's queryset respects soft deletion. ## The F() case changed in Django 6.0 A view that counts page views often does this: ```python from django.db.models import F page.view_count = F("view_count") + 1 page.save(update_fields=["view_count"]) ``` The database computes the increment, which avoids lost updates between concurrent requests. What `page.view_count` holds afterwards depends on the version: | Version | After `save()` | Risk | |---|---|---| | Django 5.2 LTS and earlier | the `F()` expression object, not a number | rendering shows the expression; a second `save()` applies `+ 1` again | | Django 6.0 and later | the new value, refreshed on `save()` | none; on MySQL and MariaDB reading it triggers a refresh query | On SQLite, PostgreSQL and Oracle, 6.x reads the new value back in the same statement; on MySQL and MariaDB the refresh is deferred until the attribute is read. Code written for older releases often calls `refresh_from_db(fields=["view_count"])` right after such a save; on 6.x that call is redundant but harmless. ## When you still need it 1. **After `QuerySet.update()`** on rows you also hold as instances, including in tests that assert on the instance. 2. **Before a read-modify-write** on a row others may have changed: reload with `select_for_update()` inside a transaction, then modify and save. 3. **After calling code that saves a different instance** of the same row, such as a service function that loads the page itself. 4. **When a long-lived instance**, held by a management command or a task, must see recent edits before acting. ## In tests and async code Tests are where stale instances show up most. Django's own docs illustrate it: create an object, run `MyModel.objects.filter(pk=obj.pk).update(val=F("val") + 1)`, and the instance still holds the old value until `obj.refresh_from_db()` is called; only then does the assertion on the new value pass. A test that asserts on an instance after calling code that updates through a queryset, or through another instance, needs the same reload. In async views and tasks, use `await page.arefresh_from_db()`, which accepts the same `using`, `fields` and `from_queryset` arguments. ## Mistakes to avoid - Expecting `page` to change after `WikiPage.objects.filter(pk=page.pk).update(...)`. - Calling `refresh_from_db()` to "commit" something: Django writes on `save()` in autocommit mode; reloading never writes. - Reloading everything in a loop over many rows, which costs a query per row; reload only what the next step needs. - Assuming a reload refreshes annotations from the original query; it reloads model fields only.
- On Django 5.2 LTS, why does saving a page twice after page.view_count = F("view_count") + 1 count two views?On 5.2 the attribute keeps the `F()` expression after the first `save()`, so the second `save()` sends `view_count = view_count + 1` again. Call `page.refresh_from_db(fields=["view_count"])` after the first save, or upgrade: from Django 6.0 the value is refreshed on `save()` and the expression is gone.
- Does refresh_from_db() reload annotations such as num_revisions from the original Django queryset?No. It reloads model fields only; annotations and `@cached_property` values stay as they were. To get fresh annotated values, run the annotated query again, or pass `from_queryset=` with a queryset that annotates them if you need them on the same instance.
saying these in an interview costs you the question
- Django updates every loaded instance of a row when the row changes.
- QuerySet.update() also updates instances already held in memory.
- In Django 6.1, an F() field still holds the expression after save().
- refresh_from_db() reloads annotations from the original query.
- refresh_from_db() is needed to commit the instance to the database.