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

DRF序列化器validate方法并行查询下失效问题求助

解决DRF并发请求下重复创建影片数据的问题

你的问题是典型的**竞态条件(Race Condition)**导致的:两个并行请求在执行序列化器validate方法时,都查询到数据库中没有对应影片,随后同时执行保存操作,最终导致重复数据入库。序列化器的validate仅能在内存层面做前置检查,无法应对并发场景下的数据库状态变化,必须结合数据库约束或事务锁来解决。

最优方案:数据库层面添加唯一约束

数据库级别的约束是防止重复数据最可靠的手段,无论业务层代码如何操作,数据库都会直接拦截违反约束的写入请求。

根据你的需求(不区分标题、导演的大小写),可以在Film模型中添加基于字段小写转换的组合唯一约束:

from django.db import models
from django.db.models.functions import Lower

class Film(models.Model):
    title = models.CharField(max_length=255)
    release_date = models.DateField()
    director = models.CharField(max_length=255)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                Lower('title'),
                'release_date',
                Lower('director'),
                name='unique_film_identity'
            )
        ]
  • 该约束会将title和director转换为小写后,与release_date组合作为唯一标识,实现不区分大小写的重复校验。
  • 注意:MySQL 8.0+、PostgreSQL等主流数据库均支持函数式唯一约束,若使用旧版数据库,可提前在模型中存储小写后的字段(如title_lower)并建立普通唯一约束。

业务层适配异常处理

添加数据库约束后,需要在视图层捕获数据库抛出的IntegrityError,转换为DRF友好的验证错误:

from django.db import IntegrityError
from rest_framework import generics
from rest_framework import serializers

class FilmCreateAPIView(generics.CreateAPIView):
    serializer_class = FilmSerializer

    def perform_create(self, serializer):
        try:
            serializer.save()
        except IntegrityError:
            raise serializers.ValidationError('Film with such parameters (title, director, release date) already exists')

补充方案:业务层事务+行锁

如果暂时无法修改数据库约束,可在视图层通过原子事务和行锁来避免竞态:

from django.db import transaction
from rest_framework import generics
from rest_framework import serializers

class FilmCreateAPIView(generics.CreateAPIView):
    serializer_class = FilmSerializer

    @transaction.atomic
    def perform_create(self, serializer):
        validated_data = serializer.validated_data
        # 使用select_for_update锁定查询结果,确保并发请求串行处理
        existing_film = Film.objects.filter(
            title__iexact=validated_data['title'],
            release_date=validated_data['release_date'],
            director__iexact=validated_data['director']
        ).select_for_update().first()

        if existing_film:
            raise serializers.ValidationError('Film with such parameters already exists')
        serializer.save()
  • @transaction.atomic确保整个查询-保存操作是原子性的;
  • select_for_update会锁定查询到的行(若存在),未查询到则后续的保存操作会在事务内完成,避免两个请求同时创建重复数据。

注意事项

  • 优先选择数据库唯一约束:业务层的锁机制仅能覆盖API请求场景,若存在后台管理、脚本等其他数据写入渠道,数据库约束是唯一可靠的拦截手段;
  • 避免依赖序列化器的validate方法做并发校验:该方法无法感知其他请求对数据库的修改,仅适合做业务规则的前置校验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 22:43:11