Django ORM 里面一个容易被忽略的幂等性

不考虑并发写的情况, Django ORM 的 save 方法也不是幂等的, 而且比较容易被忽略, 比如

def f1(a):
    a.balance = F('balance') + 1
    a.save()

def f2(a):
    a.status = 1
    a.save()

a = Account.objects.create(balance=0, status=0)
with transaction.atomic():
    f1(a)
    ...
    f2(a)

最终数据库里 balance 的值是 2

因为执行 f2 的时候, a.balance 不是一个数值, 依然是一个表达式, f2 中执行 save 会再次对 balance 加 1

避免此类隐藏 bug 的方案是
1). 使用 F 函数更新 Django Model 时, save 以后强制执行一次数据库读取, 即 a.refresh_from_db(), 让 a.balance 变成一个数值
2). save 方法永远都带上 update_fields, 避免更新当前上下文以外的字段, 让 f2 方法提交的 SQL 只更新 status 的值

方案 1 是最保险的, 但个人建议是在大多数情况下, 还是采用方案 2 的编码规范