关于Django ORM save方法竞态条件与ACID隔离性的技术问询
问题分析与解决方案
你的判断是对的
开发服务器是单进程单线程模式,不会出现多个请求同时写入数据库的情况,所以validate_unique的重复检查和后续的save操作能按顺序执行,不会出问题。但到了生产环境,gunicorn/uvicorn是多进程运行,多个请求会同时操作数据库:
当请求A刚完成重复检查(没找到重复数据),还没执行save的时候,请求B也做了同样的检查——这时候A的写入还没提交,B看不到这条未提交的数据,也会判断没有重复,接着两个请求都执行save,最终就产生了重复数据。
你提到的“save仅保证原子性,无法保证ACID隔离性”切中了核心:save的原子性只是保证单条记录的写入不会被拆分,但validate_unique里的查询和save是两个独立的数据库操作,中间没有任何隔离机制。在数据库默认的隔离级别下,当前事务看不到其他未提交的写入,自然会出现这种竞态问题。
用get_or_create的思路是对的,但要注意前提
直接替换成get_or_create不能彻底解决问题,关键是必须在数据库层面添加唯一约束:
- 先给模型加联合唯一约束,让数据库本身拦截重复数据:
class SomeModel(models.Model): name = models.CharField(max_length=255) associated_model = models.ForeignKey(AssociatedModel, on_delete=models.CASCADE) # 其他字段... class Meta: constraints = [ models.UniqueConstraint( fields=['name', 'associated_model'], name='unique_name_with_associated' ) ]
如果是通过关联模型的某个属性(比如associated_model__attr)判断重复,要调整约束字段组合来匹配这个条件。
- 然后在视图层使用
get_or_create,同时捕获极端并发下可能触发的IntegrityError,捕获后重新查询已有实例即可:
from django.db import IntegrityError try: instance, created = SomeModel.objects.get_or_create( name=given_name_from_user, associated_model=target_associated_instance, defaults={"other_field": value} ) except IntegrityError: # 出现冲突,重新查询已有实例 instance = SomeModel.objects.get( name=given_name_from_user, associated_model=target_associated_instance )
其他可选方案
如果一定要在模型层处理,可以用select_for_update加锁,但这种方式开销大且不如数据库约束可靠:
from django.db import transaction def validate_unique(self, *args, **kwargs): super().validate_unique(*args, **kwargs) with transaction.atomic(): # 加锁查询,阻止其他事务插入相同记录 exists = SomeModel.objects.select_for_update().filter( name=self.name, associated_model__attr=some_attr ).exists() if exists: raise ValueError("这条记录已经存在")
这种方式会增加数据库锁的压力,若事务边界处理不当仍可能出问题,所以优先推荐数据库约束+get_or_create的组合方案。
总结
- 你的核心判断完全正确,问题根源是并发场景下的竞态条件,save的原子性覆盖不了“查询-写入”的组合操作。
- 用
get_or_create是正确的方向,但必须配合数据库唯一约束,否则依然会出现重复数据。 - 最可靠的方案是先添加数据库约束,再在视图层用
get_or_create处理,不要依赖模型层的validate_unique做重复检查——这种检查在并发场景下根本靠不住。
内容的提问来源于stack exchange,提问作者LiorA
相关产品推荐
相关产品推荐

