多线程脚本导致Django数据库条目序号无法正确递增的问题
解决多线程下PropertyImage的order字段重复问题
你的问题核心是并发请求同时读取同一property_id的图片计数,导致多个请求生成相同的order值。之前尝试的select_for_update无效,是因为当该property_id还没有图片时,没有行被锁定,并发请求依然能同时执行计数操作。以下是几种可靠的解决办法:
方案1:使用独立计数器表(推荐)
创建一个专门记录每个property_id下一张图片序号的表,通过原子更新来获取唯一的order值,完全避免并发冲突。
步骤1:创建计数器模型
from django.db import models class PropertyImageCounter(models.Model): property_id = models.CharField(primary_key=True, max_length=255) # 与你的property_ad_id类型匹配 next_order = models.IntegerField(default=1)
步骤2:修改create方法逻辑
from django.db import transaction, F def create(self, validated_data): property_id = self.context['property_ad_id'] with transaction.atomic(): # 原子获取或创建计数器,确保同一property_id只有一行记录 counter, _ = PropertyImageCounter.objects.get_or_create( property_id=property_id, defaults={'next_order': 1} ) # 原子递增计数器,同时获取当前的order值 current_order = counter.next_order PropertyImageCounter.objects.filter(property_id=property_id).update( next_order=F('next_order') + 1 ) # 创建新图片 return PropertyImage.objects.create(ad_id=property_id, order=current_order, **validated_data)
这个方法的优势是锁粒度极小(仅操作计数器的一行),并发性能高,逻辑清晰,完全避免重复序号问题。
方案2:使用PostgreSQL Advisory Lock(无需额外表)
利用PostgreSQL的咨询锁(Advisory Lock),针对特定property_id加锁,确保同一时间只有一个请求能计算order并创建图片。
from django.db import connection, transaction def create(self, validated_data): property_id = self.context['property_ad_id'] with transaction.atomic(): # 将字符串property_id转换为整数锁键(PostgreSQL咨询锁需要整数参数) lock_key = hash(property_id) # 获取咨询锁 with connection.cursor() as cursor: cursor.execute("SELECT pg_advisory_lock(%s)", [lock_key]) # 此时可以安全计算order,因为同一property_id的请求会被阻塞直到锁释放 order = PropertyImage.objects.filter(ad_id=property_id).count() + 1 image = PropertyImage.objects.create(ad_id=property_id, order=order, **validated_data) # 显式释放锁(事务结束时也会自动释放,显式释放更稳妥) with connection.cursor() as cursor: cursor.execute("SELECT pg_advisory_unlock(%s)", [lock_key]) return image
这个方法不需要额外创建表,直接利用数据库的锁机制控制并发,适合不想新增模型的场景。
为什么之前的尝试失败?
select_for_update().count():当property_id还没有图片时,没有行被锁定,并发请求会同时得到count=0,进而生成相同的order=1。- 错误使用
F('order') + 1:你更新的是现有图片的order值,而不是为新图片分配唯一序号,逻辑方向错误。
内容的提问来源于stack exchange,提问作者Olusola Caleb
相关产品推荐
相关产品推荐

