具有嵌套 connectOrCreate 的 Prisma 多个创建查询抛出唯一约束失败

Posted

技术标签:

【中文标题】具有嵌套 connectOrCreate 的 Prisma 多个创建查询抛出唯一约束失败【英文标题】:Prisma multiple create queries with nested connectOrCreate throws unique constraint failed 【发布时间】:2021-10-05 07:31:53 【问题描述】:

我正在尝试创建一个模型,其中电影和流派这两种类型之间存在多对多关系。 我从外部数据源异步检索电影列表,create 如果尚未创建,则每个都在我自己的数据库中,然后返回电影。

const retrieveList = async () => 
   const results = await API.getMovies();
   return results.map((item) => getMovieItem(item.id));


const getMovieItem = async (movieId) => 
    // see if is already in database
    const movie = await prisma.movie.findUnique(
        where:  tmdbId: movieId ,
    );
    if (movie) 
        return movie;
     else 
        // create and return if doesn't exist
        const details = await API.getDetails(movieId);
        const genresData = details.genres.map((genre) => (
            create: 
                name: genre.name,
            ,
            where: 
                name: genre.name
            ,
        ));
        return prisma.movie.create(
            data: 
                title: details.title,
                description: details.overview,
                genres: 
                    connectOrCreate: genresData,
                ,
            ,
            select: 
                tmdbId: true,
                id: true,
                title: true,
                description: true,
                //...
            ,
        );
    
;

问题在于似乎存在某种不一致,如果在某些运行中与外部 API 的连接速度足够慢,那么一切似乎都运行良好;但如果它足够快,它也会抛出错误:

Invalid `prisma.movie.create()` invocation:
  Unique constraint failed on the fields: (`name`)"

可能是因为它试图创建一个已经创建的流派,而不是连接它。

【问题讨论】:

【参考方案1】:

作为the docs states:

作为并发事务运行的多个connectOrCreate 查询可能会导致竞争条件。 ...

因此,如果您同时请求 2 部相同类型的电影,但该类型尚不存在,则两个查询都会尝试创建该类型,只有第一个会成功。

Docs 建议捕获特定错误:

要解决这种情况,我们建议捕获唯一的违规异常(PrismaClientKnownRequestError,错误P2002)并重试失败的查询。

您也可以先创建所有流派,例如:

// Some abstract function which extracts all the genres
const genres = gatherGenres(results);

await prisma.genres.createMany(
    data: genres,
    // Do nothing on duplicates
    skipDuplicates: true,
);

然后创建所有电影并将它们连接到流派,因为流派已经创建。

另外,小 nitpick,你的函数 retrieveList 是不可等待的,因为它返回一个承诺数组,实际上要等到所有承诺完成,你需要在它上面使用 Promise.all

const retrieveList = async () => 
   const results = await API.getMovies();
   return results.map((item) => getMovieItem(item.id));


// ...

// You can just await it
await retrieveList()

// Need to use Promise.all
await Promise.all(retrieveList())

【讨论】:

感谢您的回答。因此,如果我要使用第二种方法,事先在数据库中收集所有可能的类型;您对在 apollo-server (graphql) 应用程序中执行查询的位置有什么建议吗? 我会这样做:从 api 获取所有电影,收集所有类型并创建它们,然后创建所有电影(并将它们连接到类型)。只需在 retrieveList 函数中完成所有操作,请注意 await

以上是关于具有嵌套 connectOrCreate 的 Prisma 多个创建查询抛出唯一约束失败的主要内容,如果未能解决你的问题,请参考以下文章

pr嵌套效果只有一部分

五十六. playbook基础 playbook进阶

在具有共享 NFS 挂载的服务器上运行 playbook

ansible——playbook剧本

原创ansible-playbook 详解

自动化运维管理工具 Ansible的详细解读之inventory 主机清单和playbook剧本