Django中使用get_or_create创建模型时如何锁定读取避免并发异常?
解决Django get_or_create并发下对象初始化不完整的问题
核心问题
并发场景下,get_or_create创建对象后,第三方接口调用(慢操作)还未完成、维度数据未保存时,第二个请求会读取到状态不完整的对象,导致后续依赖维度的代码逻辑异常。
解决方案:事务+行级锁
利用Django的事务和数据库行级锁,将对象创建+维度数据初始化的整个流程变成原子操作,确保其他请求只能等到整个流程完成后,才能读取到完整的对象。
代码实现
from django.db import transaction with transaction.atomic(): # 使用select_for_update对目标行加排他锁 model, created = SomeModel.objects.select_for_update().get_or_create( user=user, name=name, defaults={...} # 初始字段值 ) if created: # 调用第三方接口获取维度数据 created_dimensions = get_some_dimensions() # 关联维度数据并保存 model.dimension_values.set(created_dimensions) model.save() # 依赖模型维度数据的代码
原理说明
select_for_update()会对查询到的数据库行加排他锁,其他事务尝试读取或修改该行时,会被阻塞直到当前事务提交。transaction.atomic()确保整个代码块内的操作要么全部完成提交,要么全部回滚,避免出现中间状态的对象被读取。- 只有当维度数据处理完成、事务提交后,其他请求才能获取到完整的对象状态。
备选方案:增加状态字段(需修改模型)
如果无法依赖数据库行级锁,可以给模型增加状态字段标记对象是否初始化完成:
- 先给
SomeModel添加状态字段:
class SomeModel(models.Model): # 原有字段... STATUS_CHOICES = ( ('pending', '初始化中'), ('complete', '已完成'), ) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
- 业务代码实现:
from django.db import transaction import time with transaction.atomic(): model, created = SomeModel.objects.get_or_create( user=user, name=name, defaults={'status': 'pending', ...} ) if created: created_dimensions = get_some_dimensions() model.dimension_values.set(created_dimensions) model.status = 'complete' model.save() else: # 如果对象存在但未初始化完成,加锁等待状态更新 model = SomeModel.objects.select_for_update().get(user=user, name=name) while model.status != 'complete': time.sleep(0.1) # 短暂休眠后刷新状态 model.refresh_from_db() # 此处model的status必定为complete,可安全执行依赖维度的代码
注意事项
select_for_update()依赖数据库对行级锁的支持,需使用InnoDB(MySQL)、PostgreSQL等支持行级锁的存储引擎,MyISAM不适用。- 事务执行时间会因第三方接口的延迟而变长,需评估并发场景下的锁等待对系统性能的影响。
内容的提问来源于stack exchange,提问作者paul23
相关产品推荐
相关产品推荐

