嘿,各位Django航海家们!今天咱们不聊虚的,来点实实在在的硬货——如何优雅地(或者说,如何用最Django的方式)修改数据库里的记录。
想象一下这个场景:你老板突然说,“把所有‘待处理’的订单状态都改成‘已发货’,并且给VIP用户的订单额外加10个积分。”你脑子里是不是立马闪现出一个for循环,然后order.status = '已发货',再order.save(),看着屏幕上的进度条慢慢爬?
停!快停下你危险的想法!
这么做,就像是用勺子给游泳池换水,不是不行,是实在太慢了,而且容易把自己累趴下。今天,我就是来给你送“抽水机”的——它就是Django QuerySet的记录修改魔法。
第一章:初识我们的武器库——QuerySet
在开始“整容”之前,得先搞清楚我们的手术刀是什么。QuerySet,你可以把它理解为一条懒洋洋的、但威力无穷的数据库查询命令。
它有多懒?你光是定义它,比如books = Book.objects.all(),它压根不会去动数据库。直到你真正需要数据的时候(比如打印它、遍历它),它才会不情不愿地去执行查询。
这种“懒惰”是天大的优点,意味着我们可以不断地在它上面叠加各种筛选条件,而不会产生额外的数据库开销。今天,我们聚焦在它的“破坏力”——修改数据。
第二章:闪电战——update()方法,批量修改之王
update()是QuerySet给我们的一把“尚方宝剑”,专为批量修改而生。它的特点就一个字:快!
怎么用?
简单到令人发指:模型类.objects.filter(筛选条件).update(字段=新值)
完整示例剧场①:老板的紧急任务
假设我们有一个订单模型(Order):
# models.py
class Order(models.Model):
STATUS_CHOICES = (
('pending', '待处理'),
('shipped', '已发货'),
('cancelled', '已取消'),
)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
is_vip = models.BooleanField(default=False)
points = models.IntegerField(default=0)
# 假设数据库里有一堆状态为‘pending’的订单
任务1:把所有“待处理”的订单状态都改成“已发货”。
新手村的办法(不推荐!):
orders = Order.objects.filter(status='pending')
for order in orders:
order.status = 'shipped'
order.save() # 每循环一次,就执行一条UPDATE SQL语句
如果有一万条订单,就会向数据库发送一万次UPDATE请求。数据库内心是崩溃的。
老司机的做法(推荐!):
# 一行代码,搞定所有!
Order.objects.filter(status='pending').update(status='shipped')
发生了什么?Django只会生成一条SQL语句,类似于:
UPDATE app_order SET status = 'shipped' WHERE status = 'pending';
数据库瞬间完成所有更新,效率提升了N个数量级!
任务2:给所有VIP用户的订单额外增加10个积分。
这也很简单,Django支持在update里对字段进行原地操作。
from django.db.models import F
Order.objects.filter(is_vip=True).update(points=F('points') + 10)
这里引入了F对象这个神器。F('points')代表的是数据库中points字段本身的值。F('points') + 10就是在告诉数据库:“嘿,把每个记录的points字段值拿出来,自己加10,再存回去。”
这同样是在一条SQL中完成的,避免了先查询到Python内存,计算后再保存的繁琐过程。
update()的优缺点总结:
- 优点:极致性能,原子操作(一条SQL,成功就全成功,失败就全失败)。
- 缺点:
-
- 它不会调用模型的
save()方法。 - 因此,
save()方法里可能存在的业务逻辑(比如自动更新修改时间auto_now)不会被执行! - 它也不会触发Django的信号(Signals),比如
post_save。
- 它不会调用模型的
所以,当你确定你的修改是纯粹的、直接的字段更新,且不需要触发额外逻辑时,请毫不犹豫地使用update()。
第三章:精准手术——save()方法,精细化操作
那如果我们的修改很复杂,需要用到模型的方法,或者必须触发信号呢?这时,save()方法就派上用场了。
怎么用?
就是我们最熟悉的“先获取,再修改,后保存”三部曲。
完整示例剧场②:复杂的用户升级逻辑
假设有个用户模型(UserProfile):
class UserProfile(models.Model):
username = models.CharField(max_length=100)
level = models.CharField(max_length=20, default='bronze')
total_spent = models.DecimalField(max_digits=10, decimal_places=2, default=0)
def upgrade_level(self):
"""一个复杂的升级逻辑"""
if self.total_spent > 1000:
self.level = 'platinum'
elif self.total_spent > 500:
self.level = 'gold'
elif self.total_spent > 100:
self.level = 'silver'
# 可能还会发送邮件、通知等...
self.save()
现在,我们要给某个用户充值,并触发他的升级逻辑。
# 1. 精准获取一个对象
user = UserProfile.objects.get(username='大壮')
# 2. 修改属性
user.total_spent += 888
# 3. 调用save(),这会自动执行升级逻辑,并触发所有信号!
user.save()
# 或者,更清晰地调用自定义方法
user.upgrade_level()
在这个过程中,save()方法被完整执行,模型的auto_now字段会更新,任何监听post_save信号的代码也会被触发。
save()的优缺点总结:
- 优点:逻辑完整,会触发
save()方法和所有信号。 - 缺点:批量操作时性能极差(N+1查询问题)。
第四章:军团作战——bulk_update(),性能与功能的平衡术
Django 3.2 带来了一个强大的折中方案:bulk_update()。它允许你批量更新多个模型实例,但仍然不触发信号和save()方法。
适用场景:你需要更新大量对象,这些对象的新值已经在Python内存中计算好了,但你依然想通过一次或少数几次SQL操作来完成。
完整示例剧场③:批量计算后更新
# 假设我们有一批书,需要根据一个复杂的算法重新计算价格
books = Book.objects.filter(category='programming')
# 在Python中循环计算新价格(这里只是简单示例)
for book in books:
book.price = calculate_new_price(book) # 假设的复杂计算函数
# 使用bulk_update,指定要更新的字段
Book.objects.bulk_update(books, ['price'])
这会把所有books的price更新,通过一条(或少量,取决于数据库限制)SQL语句发送到数据库。它比循环save()快得多,但又比update()更灵活,因为允许你先进行复杂的Python逻辑计算。
bulk_update的优缺点:
- 优点:在需要复杂预处理的大批量更新中,性能远超循环
save()。 - 缺点:和
update()一样,不触发save()和信号。
第五章:总结与抉择——你的改数据决策树
好了,手术刀都介绍完了,到底该用哪把?记住下面这个决策树,你就是团队最亮的崽:
- 问:要改很多条记录,且修改逻辑能用简单的数据库表达式(比如F对象)描述吗?
-
- 是 -> 恭喜!直接用
update(),性能爆炸! - 否 -> 进入第2步。
- 是 -> 恭喜!直接用
- 问:需要触发模型的
save()方法、自定义逻辑或Django信号吗?并且只改少数几条记录?
-
- 是 -> 老老实实用
save(),逻辑完整最重要。 - 否 -> 进入第3步。
- 是 -> 老老实实用
- 问:需要更新大量记录,但这些记录的新值必须先通过复杂的Python逻辑计算出来?
-
- 是 -> 这就是
bulk_update()的完美舞台! - 否 -> 回去重新想清楚你的需求!
- 是 -> 这就是
最后的友情提醒(老司机翻车经历):
get()之后不能用update():obj = MyModel.objects.get(id=1)之后,obj.update(...)是会报错的!update()是QuerySet的方法,不是单个对象的方法。单个对象请用save()。- 事务是保障:在进行任何重要的批量更新前,考虑用
django.db.transaction.atomic()把它包起来,确保要么全部成功,要么全部回滚,避免出现“改了一半”的尴尬局面。
希望这篇充满“网感”的教程,能让你对Django QuerySet的修改操作有全新的认识。别再让数据库在你的循环里哭泣了,拿起这些高效的工具,去享受代码带来的速度和激情吧!

被折叠的 条评论
为什么被折叠?



