Django:保存()与update()更新数据库?

rsaldnfx  于 2022-12-14  发布在  Go
关注(0)|答案(9)|浏览(184)

我正在写一个Django应用程序,我需要一个函数来更新数据库中的一个字段。有没有理由使用其中一个方法而不是另一个?

def save_db_field(name,field,value):
    obj = MyModel.objects.get(name=name)
    obj.field = value
    obj.save()

def update_db_field(name,field,value):
    MyModel.objects.get(name=name).update(field=value)

看起来第二种更好,因为它在一个DB调用中完成,而不是两个。有什么原因可以解释为什么先获取,然后更新更好吗?

c9qzyr3d

c9qzyr3d1#

有几个关键的区别。
update用于查询集,因此可以一次更新多个对象。
正如@FallenAngel所指出的,定制save()方法的触发方式存在差异,但记住signalsModelManagers也很重要。我构建了一个小的测试应用程序来展示一些有价值的差异。我使用的是Python 2.7.5,Django==1.7.7和SQLite,注意最终的SQL在不同版本的Django和不同的数据库引擎上可能会有所不同。
好了,下面是示例代码。
models.py

from __future__ import print_function
from django.db import models
from django.db.models import signals
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver

__author__ = 'sobolevn'

class CustomManager(models.Manager):
    def get_queryset(self):
        super_query = super(models.Manager, self).get_queryset()
        print('Manager is called', super_query)
        return super_query

class ExtraObject(models.Model):
    name = models.CharField(max_length=30)

    def __unicode__(self):
        return self.name

class TestModel(models.Model):

    name = models.CharField(max_length=30)
    key = models.ForeignKey('ExtraObject')
    many = models.ManyToManyField('ExtraObject', related_name='extras')

    objects = CustomManager()

    def save(self, *args, **kwargs):
        print('save() is called.')
        super(TestModel, self).save(*args, **kwargs)

    def __unicode__(self):
        # Never do such things (access by foreing key) in real life,
        # because it hits the database.
        return u'{} {} {}'.format(self.name, self.key.name, self.many.count())

@receiver(pre_save, sender=TestModel)
@receiver(post_save, sender=TestModel)
def reicever(*args, **kwargs):
    print('signal dispatched')

views.py

def index(request):
    if request and request.method == 'GET':

        from models import ExtraObject, TestModel

        # Create exmple data if table is empty:
        if TestModel.objects.count() == 0:
            for i in range(15):
                extra = ExtraObject.objects.create(name=str(i))
                test = TestModel.objects.create(key=extra, name='test_%d' % i)
                test.many.add(test)
                print test

        to_edit = TestModel.objects.get(id=1)
        to_edit.name = 'edited_test'
        to_edit.key = ExtraObject.objects.create(name='new_for')
        to_edit.save()

        new_key = ExtraObject.objects.create(name='new_for_update')
        to_update = TestModel.objects.filter(id=2).update(name='updated_name', key=new_key)
        # return any kind of HttpResponse

这导致了以下SQL查询:

# to_edit = TestModel.objects.get(id=1):
QUERY = u'SELECT "main_testmodel"."id", "main_testmodel"."name", "main_testmodel"."key_id" 
FROM "main_testmodel" 
WHERE "main_testmodel"."id" = %s LIMIT 21' 
- PARAMS = (u'1',)

# to_edit.save():
QUERY = u'UPDATE "main_testmodel" SET "name" = %s, "key_id" = %s 
WHERE "main_testmodel"."id" = %s' 
- PARAMS = (u"'edited_test'", u'2', u'1')

# to_update = TestModel.objects.filter(id=2).update(name='updated_name', key=new_key):
QUERY = u'UPDATE "main_testmodel" SET "name" = %s, "key_id" = %s 
WHERE "main_testmodel"."id" = %s' 
- PARAMS = (u"'updated_name'", u'3', u'2')

我们只有一个针对update()的查询和两个针对save()的查询。
接下来,我们来讨论覆盖save()方法。显然,save()方法只调用一次。值得一提的是,.objects.create()也调用save()方法。
但是update()在模型上不调用save(),如果update()没有调用save()方法,那么信号也不会触发,输出:

Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

# TestModel.objects.get(id=1):
Manager is called [<TestModel: edited_test new_for 0>]
Manager is called [<TestModel: edited_test new_for 0>]
save() is called.
signal dispatched
signal dispatched

# to_update = TestModel.objects.filter(id=2).update(name='updated_name', key=new_key):
Manager is called [<TestModel: edited_test new_for 0>]

如你所见,save()触发Managerget_queryset()两次,而update()只触发一次。
解决方案。如果您需要“静默”更新您的值,而不调用save()-请使用updatelast_seen用户字段。当您需要正确更新模型时,请使用save()

gk7wooem

gk7wooem2#

两者看起来相似,但有一些关键点:

  1. save()将触发任何被覆盖的Model.save()方法,但update()不会触发此操作并在数据库级别进行直接更新。因此,如果您有一些带有被覆盖的保存方法的模型,则必须避免使用update或找到其他方法来执行您正在对被覆盖的save()方法执行的任何操作。
  2. obj.save()可能会有一些副作用,如果你不小心的话。你用get(...)来检索对象,所有的模型字段值都会传递给你的obj。当你调用obj.save()时,django会保存当前对象的状态以记录。所以如果get()save()之间发生了一些变化,那么那些改变将会丢失.使用save(update_fields=[.....])来避免这样的问题.
    1.在Django1.5版本之前,Django在INSERT/UPDATE之前执行SELECT,因此需要2次查询执行。
    在这里,有一个很好的指南或save()update()方法以及如何执行它们。
ffscu2ro

ffscu2ro3#

保存()方法可以插入新记录和更新已有记录,一般用于保存数据库中单个记录(mysql中为行)的示例。
update()不用于插入记录,可用于更新数据库中的多个记录(mysql中的行)。

qqrboqgw

qqrboqgw4#

直接使用update更高效,还可以防止完整性问题。

来自官方文档docs.djangoproject.com/en/3.0/ref/models/querysets/#django.db.models.query.QuerySet.update

如果你只是更新一条记录,而不需要对模型对象做任何事情,最有效的方法是调用update(),而不是将模型对象加载到内存中。

e = Entry.objects.get(id=10)
e.comments_on = False
e.save()

...执行以下操作:

Entry.objects.filter(id=10).update(comments_on=False)

使用update()还可以防止出现争用情况,即在加载对象和调用保存()之间的短时间内,数据库中可能会发生一些变化。

jyztefdp

jyztefdp5#

更新只适用于更新查询集。如果你想同时更新多个字段,比如说从一个对象示例的dict中,你可以执行如下操作:

obj.__dict__.update(your_dict)
obj.save()

请记住,字典必须包含正确的Map,其中键需要是字段名,值需要是要插入的值。

3b6akqbq

3b6akqbq6#

Update将为您提供更好的性能与多个对象的查询集,因为它将使每个查询集一个数据库调用。
然而保存是有用的,因为它很容易覆盖模型中的save方法并添加额外的逻辑,例如,在我自己的应用程序中,当其他字段改变时,我更新一个日期。

Class myModel(models.Model): 
    name = models.CharField()
    date_created = models.DateField()

    def save(self):
        if not self.pk :
           ### we have a newly created object, as the db id is not set
           self.date_created = datetime.datetime.now()
        super(myModel , self).save()
xzabzqsa

xzabzqsa7#

使用_state.adding区分更新和创建https://docs.djangoproject.com/en/3.2/ref/models/instances/

def save(self, *args, **kwargs):
    # Check how the current values differ from ._loaded_values. For example,
    # prevent changing the creator_id of the model. (This example doesn't
    # support cases where 'creator_id' is deferred).
    if not self._state.adding and (
            self.creator_id != self._loaded_values['creator_id']):
        raise ValueError("Updating the value of creator isn't allowed")
    super().save(*args, **kwargs)
kuarbcqp

kuarbcqp8#

其中一个差异可能会导致很多麻烦,即save更新,但update不更新类型为DateTimeField(auto_now=True)ModificationDateTimeField的列。这些字段(应该)在对象保存到数据库时自动设置其日期。

o0lyfsai

o0lyfsai9#

一月一日

  • 可以与模型对象一起使用,但不能与**QuerySetManager对象**一起使用。
  • 使用**select_for_update()可以运行SELECT FOR UPDATE查询**。
    一个月五个月一个月
  • 可与**QuerySetManager对象一起使用,但不能与模型对象**一起使用。
  • 具有**select_for_update()的无法运行SELECT FOR UPDATE查询**。

例如,我有**Person型号**,如下所示:

# "store/models.py"

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=30)

    def __str__(self):
        return self.name

然后,您可以将**save()Person模型对象一起使用,如下所示:
x一个一个一个一个x一个一个二个x
但是,您不能将
save()QuerySet对象**一起使用,如下所示:

# "store/views.py"

from .models import Person
from django.http import HttpResponse

def test(request):
    person = Person.objects.filter(pk=1)
    person.name = 'Tom'
    person.save() # Here

    return HttpResponse("Test")

然后,出现以下错误:
属性错误:“QuerySet”对象没有属性“保存”
而且,您不能将**save()Manager对象**一起使用,如下所示:

# "store/views.py"

from .models import Person
from django.http import HttpResponse

def test(request):
    person = Person.objects
    person.name = 'Tom'
    person.save() # Here

    return HttpResponse("Test")

然后,出现以下错误:
属性错误:“Manager”对象没有属性“保存”
然后,您可以将**update()QuerySet对象**一起使用,如下所示:

# "store/views.py"

from .models import Person
from django.http import HttpResponse

def test(request):
    person = Person.objects.filter(pk=1)
    person.update(name="Tom") # Here

    return HttpResponse("Test")

而且,您可以将**update()Manager对象**一起使用,如下所示:

# "store/views.py"

from .models import Person
from django.http import HttpResponse

def test(request):
    person = Person.objects
    person.update(name="Tom") # Here

    return HttpResponse("Test")

但是,不能将**update()Person模型对象**一起使用,如下所示:

# "store/views.py"

from .models import Person
from django.http import HttpResponse

def test(request):
    person = Person.objects.get(id=1)
    person.update(name="Tom") # Here

    return HttpResponse("Test")
# "store/views.py"

from .models import Person
from django.http import HttpResponse

def test(request):
    person = Person.objects.filter(pk=1).first()    
    person.update(name="Tom") # Here

    return HttpResponse("Test")

然后,出现以下错误:
属性错误:“Person”对象没有属性“update”
例如,select_for_update()用于防止在Django中更新数据时出现争用情况(更新丢失或写入偏斜)
并且,我有**test视图**,其中包含**save()select_for_update().filter(pk=1).first()**,如下所示:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
    person = Person.objects.select_for_update().filter(pk=1).first() # Here
    person.name = 'Tom'
    person.save() # Here

    return HttpResponse("Test")

然后,当我运行**test view时,SELECT FOR UPDATEUPDATE查询将如下所示运行。* 我使用了PostgreSQL**,下面的这些日志是PostgreSQL的查询日志,您可以检查在PostgreSQL上,如何记录带有事务查询的SQL查询,例如“开始”和“COMMIT”

现在,我删除**first()以使用update()**,如下所示:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
    person = Person.objects.select_for_update().filter(pk=1) # Here
    person.update(name="Tom") # Here

    return HttpResponse("Test")

然后,当我运行**test视图时,SELECT FOR UPDATE查询没有运行,而只运行了UPDATE查询**,如下所示:

而且,我有**test视图**,其中包含**save()select_for_update().get(pk=1)**,如下所示:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
    person = Person.objects.select_for_update().get(pk=1) # Here
    person.name = 'Tom'
    person.save() # Here

    return HttpResponse("Test")

然后,当我运行**test view时,SELECT FOR UPDATEUPDATE查询**将按如下所示运行:

现在,我删除**get()以使用update()**,如下所示:

# "store/views.py"

from django.db import transaction
from .models import Person
from django.http import HttpResponse

@transaction.atomic
def test(request):
    person = Person.objects.select_for_update() # Here
    person.update(name="Tom") # Here

    return HttpResponse("Test")

然后,当我运行**test view时,SELECT FOR UPDATE query没有运行,而只运行了UPDATE query**,如下所示:

因此,带有**select_for_update()save()可以运行SELECT FOR UPDATE查询**,而带有**select_for_update()update()**则不能。

相关问题