Opened 8 years ago

Closed 8 years ago

Last modified 6 years ago

#27335 closed Cleanup/optimization (wontfix)

Avoid object save during QuerySet.update_or_create() when there were no changes

Reported by: Artem Skoretskiy Owned by: Andrew Nester
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Right now when you use QuerySet.update_or_create -- it always saves into DB even if there were no changes.

It would be great that if nothing is changed -- nothing is saved into DB. I found myself often reusing the same pattern in our projects.

Use case:

>>> from django.contrib.auth.models import User
>>> User.objects.create(is_superuser=True, username='admin', is_active=True) # generate a use in DB to work with
>>> # later in our code
>>> user, created = User.objects.update_or_create(defaults={'is_active': True}, username='admin')
# here an extra DB write though no data were changed

If you check the current code in master (https://github.com/django/django/blob/master/django/db/models/query.py#L477):

            try:
                obj = self.select_for_update().get(**lookup)
            except self.model.DoesNotExist:
                obj, created = self._create_object_from_params(lookup, params)
                if created:
                    return obj, created
            for k, v in six.iteritems(defaults):
                setattr(obj, k, v() if callable(v) else v)
            obj.save(using=self.db) # HERE IT IS => always save

If object exists in DB, it would apply all attributes from default and save it even if there were no changes in values.

We could do something like that:

            changed = False
            for k, v in six.iteritems(defaults):
                v = v() if callable(v) else v
                if v != getattr(obj, k):
                    modified = True
                    setattr(obj, k, v)
            if changed:
                obj.save(using=self.db) # save only if there were changes

If you think it is too complex for all use cases, we may put a flag for enabling / disabling this feature or have another method for this logic.

Change History (5)

comment:1 by Tim Graham, 8 years ago

Summary: Avoid object save during QuerySet.update_or_create when there were no changesAvoid object save during QuerySet.update_or_create() when there were no changes
Triage Stage: UnreviewedAccepted

comment:2 by Andrew Nester, 8 years ago

Owner: changed from nobody to Andrew Nester
Status: newassigned

comment:3 by Andrew Nester, 8 years ago

Hi @tonnzorr
As discussed in pull request linked to this ticket implementing this optimization might cause some issues not covered here previously. Some of them:

  1. Handling foreign key changes
  2. Regression of auto_now feature
  3. Updating models backed by tables with triggers and so on.

My point here is that implementing this feature requires a lot of time comparing with profit we can gain from it, so it looks like we are trying to be too smart here and it's a little bit dangerous because this optimization looks like source of potential regression.
I am just curious to know your opinion about it because my point here is that it's better to leave behavior of update_or_create as it is now.

Version 0, edited 8 years ago by Andrew Nester (next)

comment:4 by Tim Graham, 8 years ago

Resolution: wontfix
Status: assignedclosed

Closing in absence of further discussion.

comment:5 by Rich Rauenzahn, 6 years ago

I'd like to see some kind of solution (or workaround) for this -- particularly because there isn't a workaround that I can see -- except maybe putting this logic in my models save(), but then it's always active. I guess a temporary proxy model might work as well.

I have a use case where I am synchronizing two databases with different schemas as part of a migration. The sync takes a long time when updating the data even when the data is the same because it's forcing an update for data that is already synced.

I'm for making the behavior optional, though ...

Last edited 6 years ago by Rich Rauenzahn (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top