In Django, what does save(update_fields=[...]) change about the UPDATE statement, and which surprises come with it?
answer
- only the named columns
- empty list means no query
- auto_now needs to be listed
- a row that is not there
basics
~20 ssave(update_fields=[...]) writes only the named columns in an UPDATE, so other columns changed elsewhere are not overwritten. It forces an update, skips auto_now fields and derived fields not listed, and raises NotUpdated if no row matches.
solid answer
~40 sWith `update_fields`, `save()` issues an `UPDATE` that sets only those columns, instead of rewriting every field from the in-memory instance. That is smaller, and it avoids clobbering columns another request changed since you loaded the row. The surprises: it forces an update, so if no row matches Django raises `Model.NotUpdated` (since 6.0; earlier a generic `DatabaseError`); an empty iterable skips the save entirely; unknown, many-to-many or non-concrete names raise `ValueError`; only the listed fields' `pre_save()` runs, so `auto_now` timestamps are not refreshed unless listed; and a value your `save()` override derives, like a slug, is lost unless you add it. Instances loaded with `only()` or `defer()` get an automatic `update_fields` of the loaded fields.
code
python · 14 linespage = WikiPage.objects.get(pk=7)
page.title = "Installing on Linux"
# Writes only the title column; body edits made elsewhere survive
page.save(update_fields=["title"])
# auto_now field updated_at is NOT refreshed by the line above;
# list it when the timestamp should move
page.save(update_fields=["title", "updated_at"])
try:
WikiPage(pk=999, title="Ghost").save(update_fields=["title"])
except WikiPage.NotUpdated:
pass # no row with pk 999, so nothing was updatedgo deeper
Recall that update_fields limits which columns save() writes, and that the fields you name must exist on the model.
Explain the empty-list, auto_now, deferred-loading and missing-row behaviours, and how a save() override must extend update_fields.
Use update_fields to stop long-running code from clobbering concurrent edits, and know it does not settle races on the same column.
Decide where partial writes are the norm, for example tasks and hot rows, and where whole-object saves keep code simpler.
## What a plain save() writes By default, `instance.save()` on an existing row writes **every concrete field** of the model: Django builds an `UPDATE` that sets each column to the value currently held on the Python instance. Django does not track which attributes you changed. If you loaded a wiki page, changed its `title`, and saved, the `body` column is also rewritten with whatever the instance held when it was loaded. ## What update_fields changes `page.save(update_fields=["title"])` tells Django to set **only** the listed columns: ```sql UPDATE "wiki_wikipage" SET "title" = 'Installing on Linux' WHERE "id" = 7; ``` Two benefits follow: - **Smaller writes**, which matters for wide rows and large text columns. - **No clobbering of other columns.** If another request edited `body` after you loaded the page, a full save would overwrite that edit with your stale copy; saving only `title` leaves it alone. It does **not** protect the same column from concurrent edits: two requests saving `title` still race, and the last one wins. ## The rules and surprises | Situation | Behaviour | |---|---| | `update_fields=[]` (empty iterable) | the save is skipped; no query runs | | `update_fields=None` | a normal save of all fields | | a name that is not a concrete field, or is a many-to-many | `ValueError` naming the bad fields | | no row matches the primary key | `Model.NotUpdated` (Django 6.0+), "Save with update_fields did not affect any rows." | | a field with `auto_now=True` not in the list | not updated, because only listed fields' `pre_save()` runs | | a field your `save()` override derives, not in the list | computed in memory, never written | | instance loaded with `only()` / `defer()` | Django uses the loaded fields as an automatic `update_fields`, adding any deferred field you assign | | used together with `force_insert` | not allowed; `update_fields` always forces an update | ## Overrides must cooperate A `save()` override that recomputes a derived value has to include it when the caller narrowed the write. Django's own docs show the pattern for a slug: ```python def save(self, **kwargs): self.slug = slugify(self.title) if (update_fields := kwargs.get("update_fields")) is not None and "title" in update_fields: kwargs["update_fields"] = {"slug"}.union(update_fields) super().save(**kwargs) ``` The same applies to an `updated_at = DateTimeField(auto_now=True)`: code that saves with `update_fields=["title"]` leaves the timestamp untouched unless `updated_at` is listed too. ## When to use it 1. **Targeted edits in views or tasks**, such as marking a page as reviewed, where only one or two columns should change. 2. **Long-running jobs** that hold instances for a while and must not overwrite unrelated edits made meanwhile. 3. **Counters or flags on hot rows**, combined with an `F()` expression for the value itself, so the database computes it. Avoid it where the full object was edited as a whole, such as a `ModelForm` covering every field; listing fields there only adds a way to forget one. ## update_fields versus QuerySet.update() Both write a subset of columns, but they are different tools: | | `instance.save(update_fields=[...])` | `QuerySet.filter(...).update(...)` | |---|---|---| | Rows | one instance | any number of rows in one statement | | Runs your `save()` override | yes | no | | Needs a loaded instance | yes | no | | Can use `F()` expressions | yes | yes | | Instance in memory afterwards | holds the saved values | unchanged, and now stale | Choose `update_fields` when the per-instance logic in `save()` must run; choose `update()` for bulk changes where it need not. ## Diagnosing a missing write A classic bug report reads "the page title changed but the slug and the last-edited time did not". The cause is almost always a caller passing `update_fields=["title"]` while the model derives `slug` in `save()` and stamps `updated_at` with `auto_now`. The fix is either widening the caller's list or making the override add its derived fields, and a test that saves with `update_fields` and reloads the row.
- What does Django write when you save an instance loaded with WikiPage.objects.only("title")?Django treats the loaded fields as an automatic `update_fields`, so `save()` writes only `title` (plus any deferred field you assigned a value to). Columns that were never loaded are not overwritten with placeholder values, which is what makes saving a deferred instance safe.
saying these in an interview costs you the question
- update_fields prevents two requests from racing on the same column.
- auto_now fields are always refreshed, even when not in update_fields.
- An empty update_fields list saves every field.
- update_fields quietly inserts the row if it does not exist yet.
- Django tracks changed attributes and writes only those by default.