如何避免创建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
相关产品推荐
相关产品推荐

