You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程脚本导致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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 06:25:17