Hi @steliosrammos,

if you enable interactive transactions in your schema:

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

The list of operations passed to a $transaction will be done in one interactive transaction. That includes bulk operations and nested operations.

And if you use our latest release 3.10 any operations in a $transaction will always occur in one transaction even if you do not have interactive transactions enabled.

๐ŸŒ
Prisma
prisma.io โ€บ home โ€บ transactions and batch queries โ€บ transactions and batch queries โ€บ transactions and batch queries โ€บ transactions and batch queries
Transactions and batch queries (Reference) | Prisma Documentation
Transaction A: The application commits transaction A. The new rows conflict with the rows that transaction B added at step 2. This conflict can occur at the isolation level ReadCommitted, which is the default isolation level in PostgreSQL and ...
Discussions

postgresql - Prisma - Postgres transaction - problem read incremental number in concurrent transaction - Stack Overflow
I think the issue here is with the transaction isolation level used by Prisma when running the query. More on stackoverflow.com
๐ŸŒ stackoverflow.com
Prisma not properly set isolation level in MySQL and MariaDB
Bug description When I start transaction with Prisma.TransactionIsolationLevel.ReadUncommitted: Prisma runs SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED after START TRANSACTION, so it doesn'... More on github.com
๐ŸŒ github.com
5
January 31, 2024
Serializable Isolation Level on $transaction not being respected when queries.length === 1
When I set isolationLevel: Prisma.TransactionIsolationLevel.Serializable in $transaction passed a single [query] the isolation level as seen by the plpgsql function run in the transaction is read committed not serializable. More on github.com
๐ŸŒ github.com
1
July 26, 2023
Retry failed transactions due to a high isolation level
Document the the transactions might fail and that it needs to be retried in the client. I didn't find any in https://www.prisma.io/docs/concepts/components/prisma-client/transactions ยท The problem I encountered was with the Serializable isolation level. More on github.com
๐ŸŒ github.com
2
January 3, 2023
๐ŸŒ
Prisma
prisma.io โ€บ dataguide โ€บ postgresql โ€บ inserting-and-modifying-data โ€บ using-transactions
Using Transactions | Inserting and modifying data | PostgreSQL
Read uncommitted is the isolation level that offers the fewest guarantees about maintaining data consistency and isolation. While transactions using read uncommitted have certain features frequently associated with transactions, like the ability to commit multiple statements at once or to roll back statements if a mistake occurs, they do allow numerous situations where consistency can be broken.
Top answer
1 of 2
1

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;
    });
2 of 2
1

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.

  1. Transaction B: Read school data with report number x and increment to x + 1.
  2. Transaction A: Read school data with report number x and increment to x + 1.
  3. Transaction B: Commit report number x + 1 to database.
  4. 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.

๐ŸŒ
DEV Community
dev.to โ€บ ashukr22 โ€บ interactive-transactions-in-prisma-a-developers-guide-4mg6
Interactive Transactions in Prisma: A Developer's Guide - DEV Community
July 10, 2025 - const enrichedData = await fetchFromExternalAPI(); await prisma.$transaction(async (tx) => { const user = await tx.user.create({ data: userData }); await tx.profile.create({ data: { userId: user.id, ...enrichedData } }); }); โธป ยท Set Timeouts and Retries ยท You can configure timeouts and isolation level for better control: await prisma.$transaction( async (tx) => { // transaction logic }, { maxWait: 5000, // wait up to 5 seconds to start timeout: 10000, // abort if takes more than 10 seconds isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted } ); Handle Errors Gracefully ยท
๐ŸŒ
GitHub
github.com โ€บ prisma โ€บ prisma โ€บ issues โ€บ 22890
Prisma not properly set isolation level in MySQL and MariaDB ยท Issue #22890 ยท prisma/prisma
January 31, 2024 - Prisma runs SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED after START TRANSACTION, so it doesn't affect the current ongoing transaction as is stated in mysql manual https://dev.mysql.com/doc/refman/8.0/en/set-transaction.html#set-transac...
Author ย  prisma
๐ŸŒ
Prisma
prisma.io โ€บ home โ€บ prisma client api โ€บ prisma client api โ€บ prisma client api
Prisma Client API | Prisma Documentation
The transaction levels can be overridden on a per-transaction level. const prisma = new PrismaClient({ transactionOptions: { isolationLevel: Prisma.TransactionIsolationLevel.Serializable, maxWait: 5000, // default: 2000 timeout: 10000, // default: 5000 }, });
๐ŸŒ
GitHub
github.com โ€บ prisma โ€บ prisma โ€บ issues โ€บ 20408
Serializable Isolation Level on $transaction not being respected when queries.length === 1 ยท Issue #20408 ยท prisma/prisma
July 26, 2023 - For the transaction isolation to be enforced regardless of the number of queries passed. When I pass more than one query it works as expected: const [result] = await prisma.$transaction([prisma.$executeRaw`SELECT ASSERT_SERIALIZED()`, query], { isolationLevel: Prisma.TransactionIsolationLevel.Serializable })
Author ย  prisma
Find elsewhere
๐ŸŒ
GitClear
gitclear.com โ€บ open_repos โ€บ prisma โ€บ prisma โ€บ release โ€บ 4.4.0
Prisma 4.4.0 Release - GitClear
Prisma Client supports the following isolation levels if they're available in your database provider: - ReadCommitted - ReadUncommitted - RepeatableRead - Serializable - Snapshot ยท Learn more about it in our documentation. When using certain isolation levels, it is expected that a transaction ...
๐ŸŒ
Prisma
prisma.pub โ€บ queries โ€บ transactions
Transactions | Prisma Dart
May 28, 2024 - To change the isolation level, use the isolationLevel parameter: ... await prisma.$transaction( isolationLevel: TransactionIsolationLevel.serializable, (tx) async { // Code running in a transaction...
๐ŸŒ
GitHub
github.com โ€บ prisma โ€บ prisma โ€บ releases โ€บ tag โ€บ 4.2.0
Release 4.2.0 ยท prisma/prisma
Isolation levels determine what types of data leaking can occur between transactions or what data anomalies can occur.
Author ย  prisma
๐ŸŒ
Medium
medium.com โ€บ @connect.hashblock โ€บ 10-prisma-transaction-patterns-that-avoid-deadlocks-4f52a174760b
10 Prisma Transaction Patterns That Avoid Deadlocks | by Hash Block | Medium
October 14, 2025 - Keep the transaction body short; do parsing, auth, and remote calls outside. Prefer upsert + unique indexes to avoid readโ€“thenโ€“write races. For queue workers, use FOR UPDATE SKIP LOCKED. Add retries with jitter for 40P01 / 40001 failures. Limit blast radius with timeouts, and consider advisory locks for cross-table invariants. Deadlocks arenโ€™t a moral failing; theyโ€™re a coordination tax. Prisma gives us a great API, but the patterns above decide whether your service queues politely or snaps under pressure.
๐ŸŒ
Prisma
prisma.io โ€บ blog โ€บ how-prisma-supports-transactions-x45s1d5l0ww1
Learn How Prisma Supports Database Transactions | Prisma
September 8, 2020 - Isolated: Ensures that concurrently running transactions have the same effect as if they were running in serial. Durability: Ensures that after the transaction succeeded, any writes are being stored persistently.
๐ŸŒ
GitHub
github.com โ€บ prisma โ€บ prisma โ€บ issues โ€บ 17105
Retry failed transactions due to a high isolation level ยท Issue #17105 ยท prisma/prisma
January 3, 2023 - await prisma.$transaction( [ // Prisma Client operations running in a transaction... ], { isolationLevel: Prisma.TransactionIsolationLevel.Serializable, // optional, default defined by database configuration retry: 5, // optional, default is 0 } )
Author ย  prisma
๐ŸŒ
MCP Servers
mcpservers.org โ€บ home โ€บ agent skills library โ€บ prisma-client-api-transactions
prisma-client-api-transactions | Agent Skills Library
await prisma.$transaction( async (tx) => { // operations }, { maxWait: 5000, // Max wait to acquire lock (ms) timeout: 10000, // Max transaction duration (ms) isolationLevel: 'Serializable' // Isolation level } ) Automatic transactions for nested operations: // This is automatically a transaction const user = await prisma.user.create({ data: { email: '[email protected]', posts: { create: [ { title: 'Post 1' }, { title: 'Post 2' } ] }, profile: { create: { bio: 'Hello!'
๐ŸŒ
Fixdevs
fixdevs.com โ€บ home โ€บ blog โ€บ fix: prisma transaction error โ€” transaction already closed or rolled back
Fix: Prisma Transaction Error โ€” Transaction Already Closed or Rolled Back - FixDevs
May 22, 2026 - // Check current default (5000ms) // Configure per-transaction const result = await prisma.$transaction( async (tx) => { // Long-running operations const users = await tx.user.findMany(); for (const user of users) { await tx.account.update({ where: { userId: user.id }, data: { /* complex update */ }, }); } return users; }, { timeout: 30000, // 30 seconds (default: 5000ms) maxWait: 5000, // Max time to wait for transaction to start (default: 2000ms) isolationLevel: Prisma.TransactionIsolationLevel.Serializable, } );
๐ŸŒ
Parkgang
parkgang.github.io โ€บ blog โ€บ 2023 โ€บ 05 โ€บ 28 โ€บ transaction-isolation-prisma-guide
ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ๋ถ„์„: Prisma๋ฅผ ํ™œ์šฉํ•œ ์‹ค์Šต ๊ฐ€์ด๋“œ | parkgang.log
May 28, 2023 - ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€ ์„ Prisma ๋ฅผ ํ†ตํ•ด์„œ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ด ๋ณด์ž! RDBMS ์—์„œ ์ค‘์š”ํ•œ ๊ฐœ๋… ์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜ ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์˜ ๋ฌด๊ฒฐ์„ฑ ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๋ฉฐ, ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์ž‘์—…์„ ์•ˆ์ „ํ•˜๊ณ  ์ผ๊ด€๋˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทผ๋ฐ ๋ฌด์‹ฌ์ฝ” ์‚ฌ์šฉํ•˜๋˜ ํŠธ๋žœ์žญ์…˜ ์—๋„ ๊ฒฉ๋ฆฌ ์ˆ˜์ค€(isolation level) ์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•„์‹œ๋‚˜์š”?
๐ŸŒ
ZenStack
zenstack.dev โ€บ reference โ€บ limitations
Limitations | ZenStack
const prisma = new PrismaClient({ transactionOptions: { isolationLevel: Prisma.TransactionIsolationLevel.RepeatableRead, }, }); If you don't want to change the global settings, alternatively you can set the transactionIsolationLevel option when calling ZenStack's enhance API. All the transactions initiated internally by ZenStack will use the specified isolation level.
๐ŸŒ
Answer Overflow
answeroverflow.com โ€บ m โ€บ 1285171012224417845
Prisma Interactive transactions not waiting on await statement - Prisma
September 16, 2024 - When I ran the code without $transaction block, its respecting the await keyword again, and waiting for writeToStuff1() to finish before moving to writeToStuff3WDependentOnStuff1() Could someone confirm if await keyword, Prisma Interactive transactions, doesn't guarantee wait until completion of statement? If yes, any suggestions to achieve the above using $transaction but guaranteeing statement order? Thanks ... Using isolationLevel: Prisma.TransactionIsolationLevel.Serializable seems to have solved the problem.