# Transactions

## Creating and using transactions[​](#creating-and-using-transactions "Direct link to Creating and using transactions")

Transactions are created using `DataSource` or `EntityManager`. Examples:

```
await myDataSource.transaction(async (transactionalEntityManager) => {

    // execute queries using transactionalEntityManager

})
```

or

```
await myDataSource.manager.transaction(async (transactionalEntityManager) => {

    // execute queries using transactionalEntityManager

})
```

Everything you want to run in a transaction must be executed in a callback:

```
await myDataSource.manager.transaction(async (transactionalEntityManager) => {

    await transactionalEntityManager.save(users)

    await transactionalEntityManager.save(photos)

    // ...

})
```

The most important restriction when working in a transaction is to **ALWAYS** use the provided instance of entity manager - `transactionalEntityManager` in this example. DO NOT USE GLOBAL ENTITY MANAGER. All operations **MUST** be executed using the provided transactional entity manager.

## Specifying isolation level[​](#specifying-isolation-level "Direct link to Specifying isolation level")

Specifying the isolation level for the transaction can be done by supplying it as the first parameter:

```
await myDataSource.manager.transaction(

    "SERIALIZABLE",

    (transactionalEntityManager) => {},

)
```

## Supported isolation levels[​](#supported-isolation-levels "Direct link to Supported isolation levels")

Isolation level implementations are *not* agnostic across all databases. Each driver declares which levels it supports, and TypeORM will throw an error if you request an unsupported level.

* ![CockroachDB](/img/databases/cockroachdb.svg "CockroachDB")
* ![Google Spanner](/img/databases/spanner.svg "Google Spanner")
* ![MariaDB](/img/databases/mariadb.svg "MariaDB")
* ![MS SQL Server](/img/databases/mssql.svg "MS SQL Server")
* ![MySQL](/img/databases/mysql.svg "MySQL")
* ![Oracle](/img/databases/oracle.svg "Oracle")
* ![PostgreSQL](/img/databases/postgresql.svg "PostgreSQL")
* ![SAP HANA](/img/databases/sap.svg "SAP HANA")
* ![SQLite](/img/databases/sqlite.svg "SQLite")

### CockroachDB

* `READ COMMITTED` — requires the cluster setting `sql.txn.read_committed_isolation.enabled`, which is enabled by default in recent versions
* `REPEATABLE READ` — requires the cluster setting `sql.txn.repeatable_read_isolation.enabled`, which is disabled by default (introduced in v24.3.0)
* `SERIALIZABLE` (default)

CockroachDB maps weaker SQL isolation requests to stronger levels. The fallback behavior depends on the corresponding cluster settings. For example, when you request `READ UNCOMMITTED`, `READ COMMITTED` or `REPEATABLE READ` via SQL (e.g. `BEGIN TRANSACTION ISOLATION LEVEL READ UNCOMMITTED`), CockroachDB accepts the syntax and may run the transaction at a stricter level instead:

* `READ COMMITTED` → runs as:

  <!-- -->

  * `SERIALIZABLE` if `sql.txn.read_committed_isolation.enabled` is false and `sql.txn.repeatable_read_isolation.enabled` is false
  * `REPEATABLE READ` if `sql.txn.repeatable_read_isolation.enabled` is true and `sql.txn.read_committed_isolation.enabled` is false

* `READ UNCOMMITTED` → runs as:

  <!-- -->

  * `SERIALIZABLE` if `sql.txn.read_committed_isolation.enabled` is false and `sql.txn.repeatable_read_isolation.enabled` is false
  * `READ COMMITTED` if `sql.txn.read_committed_isolation.enabled` is true
  * `REPEATABLE READ` if `sql.txn.repeatable_read_isolation.enabled` is true and `sql.txn.read_committed_isolation.enabled` is false

* `REPEATABLE READ` → runs as `SERIALIZABLE` if `sql.txn.repeatable_read_isolation.enabled` is false

Learn more

* [CockroachDB Read Committed](https://www.cockroachlabs.com/docs/stable/read-committed)
* [CockroachDB Repeatable Read](https://www.cockroachlabs.com/docs/releases/v24.3.html#:~:text=sql.txn.repeatable_read_isolation.enabled%3A%20defaults%20to%20false.%20When%20set%20to%20true%2C%20the%20following%20statements%20configure%20transactions%20to%20run%20under%20REPEATABLE%20READ%20isolation%2C%20rather%20than%20being%20automatically%20interpreted%20as%20SERIALIZABLE)
* [CockroachDB Transactions](https://www.cockroachlabs.com/docs/stable/transactions)

### Google Spanner

* `REPEATABLE READ` \*
* `SERIALIZABLE`

\* `REPEATABLE READ` on Spanner is currently in [Preview](https://cloud.google.com/products#product-launch-stages) and not yet generally available. It provides snapshot isolation and permits write-skew anomalies — use `SELECT ... FOR UPDATE` when that matters.

Learn more

* [Spanner Isolation Levels](https://cloud.google.com/spanner/docs/isolation-levels)
* [Spanner Transactions](https://cloud.google.com/spanner/docs/transactions)

### MariaDB

* `READ UNCOMMITTED`
* `READ COMMITTED`
* `REPEATABLE READ`
* `SERIALIZABLE`

Learn more

* [MariaDB SET TRANSACTION](https://mariadb.com/kb/en/set-transaction/)

### MS SQL Server

* `READ UNCOMMITTED`
* `READ COMMITTED`
* `REPEATABLE READ`
* `SERIALIZABLE`
* `SNAPSHOT`

Driver-specific options

SQL Server also supports driver-specific `options.isolationLevel` and `options.connectionIsolationLevel` settings, but these are subject to an [upstream pool limitation](https://typeorm.io/docs/drivers/microsoft-sqlserver.md#connection-pool-does-not-reset-isolation-level). The top-level `isolationLevel` option covered on this page is not affected, because it is applied explicitly on each transaction.

Learn more

* [SQL Server SET TRANSACTION ISOLATION LEVEL](https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql)

### MySQL

* `READ UNCOMMITTED`
* `READ COMMITTED`
* `REPEATABLE READ`
* `SERIALIZABLE`

Aurora MySQL via RDS Data API

**Isolation levels are not supported on the `aurora-mysql` driver.** Requesting any level throws a validation error.

This is a transport limitation, not an engine limitation. Aurora MySQL the database engine [fully supports](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Reference.IsolationLevels.html) the standard set, but the `aurora-mysql` driver talks to the cluster over the stateless [RDS Data API](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) instead of a persistent MySQL protocol connection, and the Data API provides no way to attach an isolation level to a transaction:

* [`BeginTransaction`](https://docs.aws.amazon.com/rdsdataservice/latest/APIReference/API_BeginTransaction.html) accepts only `resourceArn`, `secretArn`, `database`, and `schema` — there is no isolation parameter.
* The Data API pools backend connections opaquely. A `SET TRANSACTION ISOLATION LEVEL ...` sent as a separate `ExecuteStatement` before `BeginTransaction` has no guaranteed affinity to the backend session that the transaction will run on, so the setting is silently dropped.
* [Multi-statement SQL is not supported](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.troubleshooting.html), so `SET TRANSACTION ...; START TRANSACTION;` cannot be sent as a single call either.
* MySQL rejects `SET TRANSACTION ISOLATION LEVEL` inside an already-started transaction with [error 1568](https://dev.mysql.com/doc/refman/8.0/en/set-transaction.html), so the approach used for Aurora PostgreSQL (issuing `SET` as the first statement inside the started transaction) is not available on MySQL.

Aurora MySQL via the `mysql` driver

If per-transaction isolation levels are needed against an Aurora MySQL cluster, use the standard `mysql` driver pointed at the cluster writer endpoint instead of `aurora-mysql`. That path uses a regular MySQL protocol connection (via `mysql2`) and supports the full set of isolation levels listed above.

Learn more

* [MySQL SET TRANSACTION](https://dev.mysql.com/doc/refman/8.0/en/set-transaction.html)

### Oracle

* `READ COMMITTED`
* `SERIALIZABLE`

Learn more

* [Oracle Data Concurrency and Consistency](https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/data-concurrency-and-consistency.html)

### PostgreSQL

* `READ UNCOMMITTED`
* `READ COMMITTED`
* `REPEATABLE READ`
* `SERIALIZABLE`

Aurora PostgreSQL

The same set of isolation levels is supported by the `aurora-postgres` driver, which talks to Aurora PostgreSQL clusters through the RDS Data API.

Learn more

* [PostgreSQL Transaction Isolation](https://www.postgresql.org/docs/current/transaction-iso.html)

### SAP HANA

* `READ COMMITTED`
* `REPEATABLE READ`
* `SERIALIZABLE`

Learn more

* [SAP HANA Transaction Isolation Levels](https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/5e7cb20c7b8c44a19cc61c64ca4fcb3b.html)

### SQLite

* `READ UNCOMMITTED` — only takes effect when [shared-cache mode](https://www.sqlite.org/sharedcache.html) is enabled; in the default mode SQLite always uses `SERIALIZABLE` regardless
* `SERIALIZABLE`

Learn more

* [SQLite Isolation](https://www.sqlite.org/isolation.html)

## Default isolation level[​](#default-isolation-level "Direct link to Default isolation level")

You can configure a default isolation level for all transactions by setting `isolationLevel` in the DataSource options:

```
const dataSource = new DataSource({

    type: "postgres",

    isolationLevel: "SERIALIZABLE",

    // ...

})
```

When set, all transactions started without an explicit isolation level will use this default. An explicit isolation level passed to `transaction()` or `startTransaction()` will override the default.

## Using `QueryRunner` to create and control state of single database connection[​](#using-queryrunner-to-create-and-control-state-of-single-database-connection "Direct link to using-queryrunner-to-create-and-control-state-of-single-database-connection")

`QueryRunner` provides a single database connection. Transactions are organized using query runners. Single transactions can only be established on a single query runner. You can manually create a query runner instance and use it to manually control transaction state. Example:

```
// create a new query runner

const queryRunner = dataSource.createQueryRunner()



// establish real database connection using our new query runner

await queryRunner.connect()



// now we can execute any queries on a query runner, for example:

await queryRunner.query("SELECT * FROM users")



// we can also access entity manager that works with connection created by a query runner:

const users = await queryRunner.manager.find(User)



// lets now open a new transaction:

await queryRunner.startTransaction()



try {

    // execute some operations on this transaction:

    await queryRunner.manager.save(user1)

    await queryRunner.manager.save(user2)

    await queryRunner.manager.save(photos)



    // commit transaction now:

    await queryRunner.commitTransaction()

} catch (err) {

    // since we have errors let's rollback changes we made

    await queryRunner.rollbackTransaction()

} finally {

    // you need to release query runner which is manually created:

    await queryRunner.release()

}
```

There are 3 methods to control transactions in `QueryRunner`:

* `startTransaction` - starts a new transaction inside the query runner instance.
* `commitTransaction` - commits all changes made using the query runner instance.
* `rollbackTransaction` - rolls all changes made using the query runner instance back.

Learn more about [Query Runner](https://typeorm.io/docs/query-runner.md).
