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

如何避免创建Artist对象时因请求重叠引发重复创建?

问题描述

客户端页面设有一个按钮,点击后会发送GET请求到/artist/接口:若用户已关联Artist对象则返回艺术家页面,若未拥有则为其创建该对象。当用户双击按钮且服务器处理速度较慢时,会出现请求重叠,最终创建出两个Artist对象。

已知限制条件:

  • Artist模型的user字段不唯一
  • 忽略客户端JavaScript防重复点击的解决方案
  • 创建Artist对象时包含基于用户头像提取主色等耗时操作
  • 要求不使用表单创建对象(后续将用表单更新用户资料)
  • 忽略User对象创建时通过信号自动生成Artist的方案

需要解决:如何避免请求重叠导致的重复创建Artist对象问题?


原视图代码

def artist(request):
    print("START")
    if request.method == "GET":
        try:
            artist = Artist.objects.get(user=request.user)
            print("artist exists")
        except Artist.DoesNotExist:
            print("artist doesn't exist")
            artist = Artist.objects.get_or_create(user=request.user)[0]
            print("a new artist object has been created")
        
        return HttpResponse(status=200)

控制台输出

START
artist doesn't exist
START
artist doesn't exist
a new artist object has been created
[15/Dec/2022 12:40:23] "GET /artist/ HTTP/1.1" 200 0
a new artist object has been created
[15/Dec/2022 12:40:23] "GET /artist/ HTTP/1.1" 200 0

解决方案

方法1:数据库事务+排他锁(单实例部署首选)

利用Django的事务机制和select_for_update()加排他锁,确保同一时间只有一个请求能进入创建流程:

from django.db import transaction

def artist(request):
    print("START")
    if request.method == "GET":
        with transaction.atomic():
            # 给查询加排他锁,其他请求会等待锁释放后再执行
            artist = Artist.objects.filter(user=request.user).select_for_update().first()
            if not artist:
                print("artist doesn't exist")
                # 执行耗时的创建逻辑(比如提取头像主色)
                # ... 这里替换为你的耗时操作代码 ...
                artist = Artist.objects.create(user=request.user)
                print("a new artist object has been created")
            else:
                print("artist exists")
        
        return HttpResponse(status=200)

原理:select_for_update()会锁定查询结果行,无匹配行时,后续请求会等待当前事务提交,此时第二个请求就能看到第一个请求创建的Artist对象,避免重复创建。

方法2:Redis分布式锁(多实例部署适用)

如果服务是多实例部署,数据库锁无法跨实例生效,用Redis锁保证同一用户同一时间只有一个请求能进入创建流程:

import redis
import time
from django.conf import settings
from django.db import transaction

# 初始化Redis连接
redis_client = redis.Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0)

def artist(request):
    print("START")
    if request.method == "GET":
        lock_key = f"artist_create_lock:{request.user.id}"
        # 设置锁,过期时间设为比创建操作最长耗时稍长的值(比如30秒)
        acquired = redis_client.set(lock_key, "locked", nx=True, ex=30)
        
        if acquired:
            try:
                artist = Artist.objects.filter(user=request.user).first()
                if not artist:
                    print("artist doesn't exist")
                    # 执行耗时的创建逻辑
                    # ...
                    artist = Artist.objects.create(user=request.user)
                    print("a new artist object has been created")
                else:
                    print("artist exists")
            finally:
                # 执行完成后释放锁
                redis_client.delete(lock_key)
        else:
            # 等待锁释放后重新查询,避免重复创建
            time.sleep(0.5)
            artist = Artist.objects.filter(user=request.user).first()
            if not artist:
                # 极端情况锁过期,用数据库锁做二次兜底
                with transaction.atomic():
                    artist = Artist.objects.filter(user=request.user).select_for_update().first()
                    if not artist:
                        artist = Artist.objects.create(user=request.user)
                        print("a new artist object has been created")
            print("artist exists (from lock wait)")
        
        return HttpResponse(status=200)

原理:Redis的setnx命令(nx=True)实现分布式锁,同一用户的请求只有一个能获取锁进入创建流程;其他请求等待锁释放后再查询,避免重复操作。

方法3:修改模型添加唯一约束(若业务允许)

如果业务上可以调整,直接给Artist模型的user字段加唯一约束,get_or_create()就会自动处理重复请求:

# models.py
class Artist(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, unique=True)
    # 其他字段...

原理:数据库会维护user字段的唯一性,第二个请求调用get_or_create()时会触发唯一约束异常,自动返回已创建的对象,不会重复生成。


内容的提问来源于stack exchange,提问作者Giovanni5454

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 05:01:36