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,isolationLeveleither viatransactionOptionson 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: COMMITIf 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;
TransactionManagerdoes not paper over this. - Adapter throws an unmapped error — translated to P2039 (or P2010 for raw queries) so the transaction unwinds cleanly.
- Transaction times out —
maxWaitexceeded →P2028(well-known code; lookup ingetErrorCode). - Connection drops — adapter raises
ConnectionClosed→P2024-style error (seedriver-adapter-utilsfor the kind list).
See also
- Driver adapters for the
Transactionand savepoint contract @prisma/client-engine-runtimeforTransactionManagerinternals@prisma/clientruntime for how$transactionenters the runtime
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.