node.js - Row level locking with Prisma and PostgreSQL - Stack Overflow
Row update with DB Row Lock (SELECT FOR UPDATE)
postgresql - supabase: `prisma migrate dev` sometimes times out (postgres advisory lock) - Stack Overflow
sql - Does Prisma support select with "with (nolock)"? - Stack Overflow
I realized, that for your specific usecase you could also increment your incrementalReportNumber with an atomic number operation
This avoids the need for a stricter transaction isolation level, because you only run update queries and avoid all read queries:
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;
});
I think the issue here is with the transaction isolation level used by Prisma when running the query.
The default transaction isolation level in Postgres is "read committed", this is what Prisma uses and it does not prevent non-repeatable reads.
Non-Repeatable Reads
If you're not sure what non-repeatable reads are, I am adding a good explanation taken from 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"
In your case, the following race condition might be happening due to non-repeatable reads.
- Transaction B: Read school data with report number x and increment to x + 1.
- Transaction A: Read school data with report number x and increment to x + 1.
- Transaction B: Commit report number x + 1 to database.
- Transaction A: Attempt to create report with x+1 report number and fail.
Unfortunately, Prisma does not currently support changing the transaction isolation level. So, you might need to come to some other workaround for this issue (possibly even writing raw SQL code).
We have a feature request for exactly this and I would really urge you to comment your problem over there. That way it would help us track demand for this feature and incentivize adding it quickly.
Transactions in Prisma aren't implemented yet. However there is some issues opened in their github about this:
- Transactions for multiple operations (#74)
- Transactions for long running operations with dependencies between write operations (#3846)
Prisma 2.1.0 released the first experimental version of transactions via Promises.
Here is the official documentation docs
const write1 = prisma.user.create()
const write2 = prisma.post.create()
const write3 = prisma.profile.create()
await prisma.$transaction([write1, write2, write3])