【问题标题】:Prisma - Postgres transaction - problem read incremental number in concurrent transactionPrisma - Postgres 事务 - 并发事务中读取增量数的问题
【发布时间】:2021-12-01 00:21:52
【问题描述】:

我目前正在尝试使用 Prisma 的新“interactiveTransactions”预览功能。但是,它似乎并没有真正将我的数据库更改作为一个事务进行。

每当我创建新报告时,我都会尝试更新学校餐桌上的增量数字。

这就是我的表格的样子:

model Report {
  id   String @id @default(cuid())
  name String
  publicId Int 
  schoolId String

  school @relation(fields: [schoolId], references: [id])
  @@unique([publicId, schoolId])
}

model School {
  id   String @id @default(cuid())
  name String
  incrementalReportNumber Int @default(1001)

  reports         Report[]
}

每当我创建新报告时,用于递增数字的代码如下所示:

 report = await prisma.$transaction(async () => {
      // get current report number
      const school = await prisma.school.findUnique({
        where: {
          id: school.id
        }
      });
      const newIncrementalReportNumber = school.incrementalReportNumber + 1;

      // Increase incrementalReportNumber
      await prisma.school.update({
        where: { id: school.id },
        data: {
          incrementalReportNumber: newIncrementalReportNumber
        }
      });

      // Create report
      const newReport = await prisma.report.create({
        data: {
          publicId: newIncrementalReportNumber,
          name: 'new report'
        },
      });

      // Return
      return newReport;
    });

当我同时创建多个报告时,我收到“唯一约束失败”错误,

the Error PrismaClientKnownRequestError: Unique constraint failed on the fields: (`publicId`,`schoolId`)

因为当我得到 incrementalReportNumber 时,一些进程具有相同的值,尽管其他进程已经递增它。

我使用 prisma 注册了“interactiveTransactions”预览功能,如下所示:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["interactiveTransactions"]
}

我正在使用 Prisma 版本“3.2.1”

【问题讨论】:

  • 能否请您提供以下详细信息:1. newIncrementalReportNumber 的值来自哪里以及 2. 您的 Prisma Schema 的确切外观是什么样的?
  • 1. newIncrementalReportNumber 显示在代码中。 2. 架构更新
  • 关于 1:在您的代码中,变量 school 包含从数据库中检索到的对象,但 newIncrementalReportNumber 被分配为比另一个名为 theBusiness 的对象的报告编号大 1,a其他地方没有提到的变量。这是一个可能的错误吗?
  • 抱歉,代码更新了
  • 谢谢,我会试试看能否找出问题所在。如果我能确定什么,会回复你!

标签: postgresql transactions prisma


【解决方案1】:

我意识到,对于您的特定用例,您还可以使用 atomic number operation 增加您的 incrementalReportNumber

这避免了对更严格的事务隔离级别的需要,因为您只运行更新查询并避免所有读取查询:

 const report = await prisma.$transaction(async (prisma) => {
      const school = await prisma.school.update({
        where: { id: schoolId },
        data: {
          incrementalReportNumber: {
            // increment the report number here
            increment: 1,
          },
        },
      });

      const newReport = await prisma.report.create({
        data: {
          publicId: school.incrementalReportNumber,
          schoolId: school.id,
          name: reportName,
        },
      });

      return newReport;
    });

【讨论】:

  • 在我看来这是一个很好的解决方案,应该可以解决问题:)
【解决方案2】:

我认为这里的问题在于 Prisma 在运行查询时使用的 transaction isolation level

Postgres 中的默认事务隔离级别是“已提交读”,这是 Prisma 使用的,它不会阻止不可重复读取。

不可重复读取

如果您不确定什么是不可重复读取,我将添加一个很好的解释,取自 here

“不可重复读取是指在同一事务中读取两次的数据不能保证包含相同的值。根据隔离级别,另一个事务可能已经介入并更新了两次读取之间的值。

发生不可重复读取是因为在较低的隔离级别读取数据只会在读取期间锁定数据,而不是在事务期间锁定”

在您的情况下,由于不可重复读取,可能会发生以下竞争条件。

  1. 事务 B:读取报告编号为 x 的学校数据并递增到 x + 1。
  2. 事务 A:读取报告编号为 x 的学校数据并递增到 x + 1。
  3. 事务 B:将报告编号 x + 1 提交到数据库。
  4. 事务 A:尝试使用 x+1 报告编号创建报告并失败。

不幸的是,Prisma 目前不支持更改事务隔离级别。因此,您可能需要针对此问题采取其他解决方法(甚至可能编写原始 SQL 代码)。

为此,我们有一个feature request,我真的强烈建议您在那里评论您的问题。这样可以帮助我们跟踪对该功能的需求并激励我们快速添加它。

【讨论】:

    猜你喜欢
    • 2015-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-22
    • 1970-01-01
    • 1970-01-01
    • 2011-05-24
    • 1970-01-01
    相关资源
    最近更新 更多