Open-Source Wikis

/

Prisma

/

Features

/

Transactions

prisma/prisma

Transactions

Two flavors: batch transactions (prisma.$transaction([...])) and interactive transactions (prisma.$transaction(async tx => {...})). Both are owned by TransactionManager in @prisma/client-engine-runtime; both flow through driver adapters' Transaction interface.

Batch transactions

const [user, posts] = await prisma.$transaction([
  prisma.user.findUnique({ where: { id: 1 } }),
  prisma.post.findMany({ where: { authorId: 1 } }),
]);

The runtime collects each query into a single transaction at the engine level. If any query throws, the whole batch rolls back.

Sequential vs parallel batch behavior is controlled by transaction options (isolationLevel).

Interactive transactions

await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({ data: { ... } })
  await tx.post.create({ data: { authorId: user.id, ... } })
})

tx is a PrismaClient proxy that pins all queries to a server-side transaction. The user can:

  • Issue any number of queries inside the callback
  • Throw to roll back; return to commit
  • Configure maxWait, timeout, isolationLevel either via transactionOptions on the constructor or on the call

Where transactions are owned

graph LR
    User[User code] --> PC[PrismaClient]
    PC --> RH[RequestHandler]
    RH --> Engine[ClientEngine]
    Engine --> Exec[Local/RemoteExecutor]
    Exec --> QI[QueryInterpreter]
    QI --> TM[TransactionManager<br/>client-engine-runtime/src/transaction-manager]
    TM --> Adapter[Driver adapter]
    Adapter -->|TransactionContext| TxStart[startTransaction]
    TxStart --> TxObj[Transaction instance]
    TxObj -->|createSavepoint, rollbackToSavepoint, releaseSavepoint| DB[(Database)]

TransactionManager (packages/client-engine-runtime/src/transaction-manager/transaction-manager.ts) is the single source of truth for:

  • Transaction IDs (used to route subsequent requests to the right transaction)
  • Isolation level handling
  • Nested transactions (implemented as savepoints)

It does not synthesize provider-specific savepoint SQL. It calls into the adapter's Transaction methods. If an adapter doesn't implement createSavepoint / rollbackToSavepoint, nested transactions on that database will fail loudly.

Savepoint dialects

Per AGENTS.md:

Provider Create Rollback to Release
PostgreSQL SAVEPOINT <name> ROLLBACK TO SAVEPOINT <name> RELEASE SAVEPOINT <name>
MySQL SAVEPOINT <name> ROLLBACK TO <name> RELEASE SAVEPOINT <name>
SQLite SAVEPOINT <name> ROLLBACK TO <name> RELEASE <name>
SQL Server SAVE TRANSACTION <name> ROLLBACK TRANSACTION <name> (no release statement)

These are surfaced as async methods on Transaction, not as a savepoint(action, name) method that returns SQL. The shift was deliberate: it moves dialect knowledge into adapters and out of the cross-database interpreter.

Sequence: nested interactive transactions

sequenceDiagram
    participant App
    participant TM as TransactionManager
    participant Tx as Transaction (adapter)
    participant DB

    App->>TM: $transaction(async tx => {...})
    TM->>Tx: begin
    Tx->>DB: BEGIN (or START TRANSACTION)
    App->>TM: $transaction inside callback (nested)
    TM->>Tx: createSavepoint(sp1)
    Tx->>DB: SAVEPOINT sp1
    App->>TM: query inside nested
    TM->>Tx: queryRaw(...)
    Tx->>DB: SQL
    DB-->>Tx: rows
    App-->>TM: nested resolves
    TM->>Tx: releaseSavepoint(sp1)
    Tx->>DB: RELEASE SAVEPOINT sp1 (PG/MySQL/SQLite)
    App-->>TM: outer resolves
    TM->>Tx: commit
    Tx->>DB: COMMIT

If the nested block throws, TM issues rollbackToSavepoint(sp1) instead of release.

Constructor option: transactionOptions

new PrismaClient({
  transactionOptions: {
    maxWait: 2000,
    timeout: 5000,
    isolationLevel: 'Serializable',
  },
});

Validated in packages/client/src/runtime/utils/validatePrismaClientOptions.ts. Per-call overrides are accepted as the second argument to $transaction.

Interactive transactions on Accelerate

When using RemoteExecutor, the transaction lives on Accelerate's side — query-plan-executor constructs and tracks the TransactionManager for that connection. The wire format carries the transaction ID so subsequent requests in the callback are routed correctly.

Failure modes

  • Driver doesn't support savepoints — nested transactions fail; TransactionManager does not paper over this.
  • Adapter throws an unmapped error — translated to P2039 (or P2010 for raw queries) so the transaction unwinds cleanly.
  • Transaction times outmaxWait exceeded → P2028 (well-known code; lookup in getErrorCode).
  • Connection drops — adapter raises ConnectionClosedP2024-style error (see driver-adapter-utils for the kind list).

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Transactions – Prisma wiki | Factory