Django中save(force_update=True)返回None,能否获取受影响行数?(PostgreSQL)
好问题!我之前在处理带auto_now=True字段的模型更新时,也遇到过类似的困惑——毕竟Django的save()方法默认返回None,没法直接拿到受影响行数,再加上auto_now字段总会自动更新,很容易混淆“真正的数据变更”和“仅自动字段更新”的情况。
下面给你几个实用的解决方案,根据你的场景选择:
1. 优先使用QuerySet的update()方法(最直接)
如果你的更新逻辑不需要触发模型的save()信号、clean()方法或者其他模型级别的钩子,用update()是最简单的方式——它会直接返回受影响的行数:
from django.utils import timezone # 假设你的模型是Address,auto_now字段是updated_at affected_rows = Address.objects.filter(pk=address.pk).update( street=address.street, city=address.city, # 因为update()不会自动处理auto_now字段,需要手动更新 updated_at=timezone.now() ) # 现在affected_rows就是受影响的行数: # - 如果至少有一个字段(包括updated_at)的值和数据库中不同,返回1 # - 如果所有字段值都和数据库一致,返回0
这个方法的好处是直观,而且能精准拿到数据库层面的受影响行数。
2. 用save()时,手动对比字段判断业务变更
如果必须使用save(force_update=True)(比如需要触发模型的钩子方法),但你想知道除了auto_now字段外,是否有业务字段发生了变更,可以在save前对比模型的原始值和当前值:
# 先获取数据库中的原始数据 original_address = Address.objects.get(pk=address.pk) # 定义需要排除的自动更新字段(比如updated_at) exclude_fields = ['updated_at'] # 检查是否有业务字段变更 has_business_changes = any( getattr(address, field.name) != getattr(original_address, field.name) for field in Address._meta.fields if field.name not in exclude_fields ) # 执行保存 address.save(force_update=True) # 此时: # - has_business_changes为True,说明有业务字段被修改 # - 即使has_business_changes为False,因为auto_now字段的更新,数据库的受影响行数还是1
3. 通过数据库连接获取最后一次UPDATE的行数(进阶)
如果你确实需要拿到save()操作对应的数据库受影响行数,可以通过Django的数据库连接来获取。不过要注意,save()内部可能执行多个查询,所以要确保拿到的是UPDATE语句的结果:
from django.db import connection # 执行保存 address.save(force_update=True) # 获取最后一次数据库操作的行数 with connection.cursor() as cursor: # PostgreSQL中可以通过GET DIAGNOSTICS获取最后一次操作的行数 cursor.execute("GET DIAGNOSTICS row_count = ROW_COUNT;") affected_rows = cursor.fetchone()[0]
不过这个方法要小心:如果你的save()触发了其他数据库操作(比如信号里的查询),拿到的可能不是你要的UPDATE行数。
关于auto_now=True的小提醒
只要你调用save(),auto_now=True的字段总会被更新,这意味着即使其他字段完全没变化,PostgreSQL执行的UPDATE语句也会修改这一行,此时受影响行数永远是1。如果想避免这种情况,可以考虑去掉auto_now=True,改为手动在需要更新的场景设置该字段的值。
内容的提问来源于stack exchange,提问作者mascDriver

