select_for_update只锁定查询返回的具体行记录,必须在transaction.atomic()中使用,配合索引字段查询和get()/first()触发,否则锁失效或退化为表锁。

select_for_update在Django里到底锁什么?
select_for_update 不是给整张表加锁,也不是锁 Python 对象,它只在数据库层面锁定查询返回的**具体行记录**(基于主键或唯一索引),且仅对支持行级锁的数据库生效(PostgreSQL、MySQL InnoDB)。如果查询没走索引、用了 select_related 跨表但没指定 for_update、或者用了 distinct() / values_list() 等导致无法明确行标识的操作,锁可能失效或退化为表锁。
怎么写才能真正生效?必须满足这三点
缺一不可:
- 查询必须在
transaction.atomic()块内执行,否则锁在语句结束就释放,起不到串行控制作用 - 必须用
get()或first()这类能明确返回单行对象的方法;filter()返回 QuerySet 本身不触发查询,得调.select_for_update().get() - 被查字段需有索引(尤其是 WHERE 条件里的字段),否则 PostgreSQL 可能锁全表,MySQL InnoDB 可能锁间隙(Gap Lock)甚至升级为表锁
正确示例:
from django.db import transaction
with transaction.atomic():
order = Order.objects.select_for_update().get(id=123)
if order.status == 'pending':
order.status = 'processing'
order.save()
常见踩坑:锁不住、死锁、超时
select_for_update 默认会一直阻塞等待,直到拿到锁或连接超时。容易出问题的点:
- 多个视图/任务按不同顺序加锁:比如 A 先锁
order_id=1再锁user_id=5,B 反过来先锁user_id=5再锁order_id=1→ 死锁 - 没设超时:用
.select_for_update(nowait=True)可抛DatabaseError避免无限等;用.select_for_update(timeout=5)(PostgreSQL 9.6+)控制最大等待秒数 - 锁了无关字段:比如
select_for_update(of=('status',))只锁 status 字段所在行,但若 WHERE 条件没覆盖这些字段,仍可能锁错行 - ORM 缓存干扰:同一事务中重复调用
.get()不会重新查库,也不会再次加锁,要确保逻辑上只查一次
和乐观锁比,什么时候该选 select_for_update?
悲观锁适合「冲突概率高 + 操作耗时短 + 必须强一致性」的场景,比如库存扣减、订单状态机流转、银行账户余额更新。不适合长事务(比如用户上传大文件期间一直持锁)、或读多写少场景(会严重拖慢并发读)。
注意:Django 的 select_for_update 在 SQLite 上完全不生效(只报 warning),生产环境务必确认 DB 引擎支持;MySQL 下还要留意隔离级别——READ COMMITTED 下锁行为与 REPEATABLE READ 有差异,特别是对不存在记录的锁处理。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











