Prisma - Postgres 事务 - 问题读取并发事务中的增量数
Prisma - Postgres transaction - problem read incremental number in concurrent transaction
我目前正在尝试使用 Prisma 的新“interactiveTransactions”预览功能。但是,它似乎并没有真正使我的数据库更改成为事务。
每当我创建新报告时,我都在尝试更新我学校 table 的增量数字。
这是我的 table 的样子:
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”
我认为这里的问题是 Prisma 在 运行 查询时使用的 transaction isolation level。
Postgres 中的默认事务隔离级别是“已提交读”,这是 Prisma 使用的,它不会阻止不可重复读取。
不可重复读取
如果您不确定什么是不可重复读取,我将添加一个很好的解释,该解释取自 here
"A non-repeatable read is one in which data read twice inside the same transaction cannot be guaranteed to contain the same value. Depending on the isolation level, another transaction could have nipped in and updated the value between the two reads.
Non-repeatable reads occur because at lower isolation levels reading data only locks the data for the duration of the read, rather than for the duration of the transaction"
在您的情况下,由于不可重复读取,可能会发生以下竞争情况。
- 事务 B:读取报告编号为 x 的学校数据并递增到 x + 1。
- 事务 A:读取报告编号为 x 的学校数据并递增到 x + 1。
- 事务 B:将报告编号 x + 1 提交到数据库。
- 事务 A:尝试创建报告编号为 x+1 的报告但失败。
不幸的是,Prisma 当前不支持更改事务隔离级别。 因此,您可能需要针对此问题采取其他解决方法(甚至可能编写原始 SQL代码)。
我们有一个 feature request 正是为了这个,我真的强烈建议你在那里评论你的问题。这样可以帮助我们跟踪对该功能的需求并激励我们快速添加它。
我意识到,对于您的特定用例,您还可以使用 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;
});
我目前正在尝试使用 Prisma 的新“interactiveTransactions”预览功能。但是,它似乎并没有真正使我的数据库更改成为事务。
每当我创建新报告时,我都在尝试更新我学校 table 的增量数字。
这是我的 table 的样子:
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”
我认为这里的问题是 Prisma 在 运行 查询时使用的 transaction isolation level。
Postgres 中的默认事务隔离级别是“已提交读”,这是 Prisma 使用的,它不会阻止不可重复读取。
不可重复读取
如果您不确定什么是不可重复读取,我将添加一个很好的解释,该解释取自 here
"A non-repeatable read is one in which data read twice inside the same transaction cannot be guaranteed to contain the same value. Depending on the isolation level, another transaction could have nipped in and updated the value between the two reads.
Non-repeatable reads occur because at lower isolation levels reading data only locks the data for the duration of the read, rather than for the duration of the transaction"
在您的情况下,由于不可重复读取,可能会发生以下竞争情况。
- 事务 B:读取报告编号为 x 的学校数据并递增到 x + 1。
- 事务 A:读取报告编号为 x 的学校数据并递增到 x + 1。
- 事务 B:将报告编号 x + 1 提交到数据库。
- 事务 A:尝试创建报告编号为 x+1 的报告但失败。
不幸的是,Prisma 当前不支持更改事务隔离级别。 因此,您可能需要针对此问题采取其他解决方法(甚至可能编写原始 SQL代码)。
我们有一个 feature request 正是为了这个,我真的强烈建议你在那里评论你的问题。这样可以帮助我们跟踪对该功能的需求并激励我们快速添加它。
我意识到,对于您的特定用例,您还可以使用 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;
});