f表达式本身不解决竞态条件,需配合update()、select_for_update()或数据库原生函数;直接f+save()因“查-算-更”三步引发覆盖;update()+f实现原子更新;需校验时用select_for_update()加锁;复杂场景优先数据库原生函数或redis分布式锁。

F 表达式本身不能解决竞态条件,它只是避免了“读-改-写”中的本地计算环节,但不提供原子性保证。真正解决竞态,必须配合数据库级别的原子操作(如 update()、select_for_update())或数据库原生函数(如 COALESCE、INCREMENT)。
为什么直接用 F + save() 会出错?
很多人以为写 obj.field = F('field') + 1; obj.save() 就能安全自增,其实这会触发三步:查出对象 → 在 Python 里算出新值 → 发起 UPDATE。中间可能被其他请求覆盖。
- 现象:并发调用后,字段只 +1 而不是 +2
- 本质:Django 把
F当作占位符传给 SQL,但save()仍走“先查后更”流程,没跳过读取 - 正确做法是绕过 ORM 实例,直接用
QuerySet.update()
用 update() + F 实现真正原子更新
这是最常用也最轻量的解法,适用于计数器、状态切换、余额加减等无需业务逻辑校验的场景。
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
-
MyModel.objects.filter(id=123).update(counter=F('counter') + 1)—— 生成单条UPDATE ... SET counter = counter + 1SQL - 返回值是受影响行数(int),不是模型实例,没法链式调用或触发信号
- 不支持跨表关联更新(比如
F('related__field')在update()中无效) - 若需更新后获取新值,得再查一次,或改用
select_for_update()
需要读取+校验时,必须用 select_for_update()
当更新前要判断当前值(比如“余额不足则拒绝”),F 不够用,必须锁定行。
- 在事务中执行:
with transaction.atomic(): obj = MyModel.objects.select_for_update().get(id=123) - 此时其他事务对同一行的
select_for_update()会阻塞,直到当前事务结束 - 注意:MySQL 默认隔离级别下,
select_for_update()只锁索引覆盖的行;PostgreSQL 锁整行 - 别在循环里用它,容易死锁;也别在长事务里持有锁太久
复杂逻辑或高并发场景,优先考虑数据库原生函数
涉及条件更新、多字段联动、或需要避免应用层参与时,直接用数据库能力更稳。
- PostgreSQL:
MyModel.objects.filter(...).update(status=Case(When(condition, then=Value('done')), default=F('status'))) - MySQL:
MyModel.objects.extra(update={'retry_count': 'retry_count + 1', 'last_retry': 'NOW()'})(注意extra()已弃用,推荐raw()或直接connection.cursor()) - Redis 做分布式锁 + 数据库兜底,适合跨服务场景,但引入新组件,复杂度上升
真正容易被忽略的是:F 表达式只是把计算挪到数据库,它不等于事务,也不等于锁。你得清楚自己处在哪个层级——是纯数值更新?要不要读后再判?有没有跨服务协调需求?选错方案,压测时才会暴露问题。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










