# Source: https://mikro-orm.io/docs/transactions.md

# Transactions and Concurrency

> Starting v3.4, transactions are also supported in [MongoDB driver](https://mikro-orm.io/docs/usage-with-mongo.md).

## Transaction Demarcation[​](#transaction-demarcation "Direct link to Transaction Demarcation")

Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very important because if not done properly it can negatively affect the performance of your application. Many databases and database abstraction layers by default operate in auto-commit mode, which means that every single SQL statement is wrapped in a small transaction. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap.

For the most part, MikroORM already takes care of proper transaction demarcation for you: All the write operations (INSERT/UPDATE/DELETE) are queued until `em.flush()` is invoked which wraps all of these changes in a single transaction.

However, MikroORM also allows (and encourages) you to take over and control transaction demarcation yourself.

These are two ways to deal with transactions when using the MikroORM and are now described in more detail.

### Approach 1: Implicitly[​](#approach-1-implicitly "Direct link to Approach 1: Implicitly")

The first approach is to use the implicit transaction handling provided by the MikroORM `EntityManager`. Given the following code snippet, without any explicit transaction demarcation:

```
const user = new User(...);
user.name = 'George';
await orm.em.persist(user).flush();
```

Since we do not do any custom transaction demarcation in the above code, `em.flush()` will begin and commit/rollback a transaction. This behavior is made possible by the aggregation of the DML operations by the MikroORM and is sufficient if all the data manipulation that is part of a unit of work happens through the domain model and thus the ORM.

### Approach 2: Explicitly[​](#approach-2-explicitly "Direct link to Approach 2: Explicitly")

The explicit alternative is to use the transactions API directly to control the boundaries. The code then looks like this:

```
await orm.em.transactional(em => {
  //... do some work
  const user = new User(...);
  user.name = 'George';
  em.persist(user);
});
```

Or you can use `begin/commit/rollback` methods explicitly. Following example is equivalent to the previous one:

```
const em = orm.em.fork();
await em.begin();

try {
  //... do some work
  const user = new User(...);
  user.name = 'George';
  em.persist(user);
  await em.commit(); // will flush before making the actual commit query
} catch (e) {
  await em.rollback();
  throw e;
}
```

Another way to handle explicit transactions is by using the `@Transactional()` decorator.

```
import { EntityManager, MikroORM, Transactional } from '@mikro-orm/core';

export class MyService {

  constructor(private readonly em: EntityManager) { }

  @Transactional()
  async doSomething() {
    //... do some work
    const user = new User(...);
    user.name = 'George';
    this.em.persist(user); 
  }

}
```

This decorator wraps the method with `em.transactional()`, so you can provide `TransactionOptions` just like with `em.transactional()`. The difference is that you can specify the context in which the transaction begins by providing `context` option, and if omitted, the transaction will begin in the current context implicitly. It works on async functions and can be nested with `em.transactional()`.

Explicit transaction demarcation is required when you want to include custom DBAL operations in a unit of work or when you want to make use of some methods of the EntityManager API that require an active transaction. Such methods will throw a `ValidationError` to inform you of that requirement.

`em.transactional(cb)` and the `@Transactional()` will flush the inner `EntityManager` before transaction commit.

### Transaction Propagation[​](#transaction-propagation "Direct link to Transaction Propagation")

Transaction propagation defines how transactions relate to each other when multiple transactional methods are called. This is particularly useful when building complex business logic that spans multiple service layers.

By default, MikroORM uses `NESTED` propagation. When you call `em.transactional()` or use `@Transactional()` decorator within another transaction, it automatically creates a savepoint (nested transaction) rather than joining the existing transaction. This allows inner transaction failures to be handled independently. You can customize this behavior using the `propagation` option in both methods:

| Propagation        | Description                                                                      |
| ------------------ | -------------------------------------------------------------------------------- |
| `NESTED` (default) | Creates savepoint if transaction exists, otherwise new transaction               |
| `REQUIRED`         | Uses existing transaction if available (no savepoint), otherwise creates new one |
| `REQUIRES_NEW`     | Always creates independent transaction, suspending existing one                  |
| `SUPPORTS`         | Uses existing transaction if available, otherwise executes non-transactionally   |
| `MANDATORY`        | Requires existing transaction, throws error if none exists                       |
| `NOT_SUPPORTED`    | Suspends existing transaction and executes without transaction                   |
| `NEVER`            | Must execute without transaction, throws error if one exists                     |

#### NESTED Propagation[​](#nested-propagation "Direct link to NESTED Propagation")

Creates a savepoint when a transaction exists, otherwise creates a new transaction. This is the default behavior.

```
// Using em.transactional()
await em.transactional(async (em1) => {
  const book = new Book(...);
  book.title = 'Domain-Driven Design';
  em1.persist(book);
  
  await em1.transactional(async (em2) => {
    const author = new Author(...);
    author.name = 'Eric Evans';
    em2.persist(author);
    throw new Error('Nested transaction failed');
  });
  
  // Book is still saved despite inner transaction failure
});

// Using @Transactional() decorator
class BookService {
    
  constructor(private readonly em: EntityManager) { }

  @Transactional()
  async createBook() {
    const book = new Book(...);
    book.title = 'Domain-Driven Design';
    this.em.persist(book);
    await this.addAuthor(); // Nested call creates savepoint
  }
  
  @Transactional()
  async addAuthor() {
    const author = new Author(...);
    author.name = 'Eric Evans';
    this.em.persist(author);
    throw new Error('Nested transaction failed');
  }
}
```

The nested call automatically creates a savepoint, allowing the inner transaction to fail independently without affecting the outer transaction.

#### SUPPORTS Propagation[​](#supports-propagation "Direct link to SUPPORTS Propagation")

Flexible propagation that adapts to the current context:

```
// Using em.transactional()
await em.transactional(async (em1) => {
  await em1.transactional(async (em2) => {
    const author = new Author(...);
    author.name = 'Jane Smith';
    author.email = 'jane@example.com';
    em2.persist(author);
  }, { propagation: TransactionPropagation.SUPPORTS });
});

// Using @Transactional() decorator
class BookService {
    
  constructor(private readonly em: EntityManager) { }

  @Transactional({ propagation: TransactionPropagation.SUPPORTS })
  async findBookWithAuthor(id: number) {
    const book = await this.em.findOneOrFail(Book, id, { populate: ['author'] });
    // Works with or without transaction
    return book;
  }
  
  @Transactional()
  async updateBookTitle(id: number, title: string) {
    // This creates a transaction
    const book = await this.findBookWithAuthor(id); // Joins existing transaction
    book.title = title;
  }
}
```

Ideal for read operations that can work with or without transactional consistency.

#### REQUIRED Propagation[​](#required-propagation "Direct link to REQUIRED Propagation")

Uses the existing transaction without creating a savepoint. All operations share the same transaction context:

```
// Using em.transactional()
await em.transactional(async (em1) => {
  const author = new Author(...);
  author.name = 'John Doe';
  author.email = 'john@example.com';
  em1.persist(author);
  
  await em1.transactional(async (em2) => {
    const book = new Book(...);
    book.title = 'Clean Code';
    book.author = author;
    em2.persist(book);
    throw new Error();
  }, { propagation: TransactionPropagation.REQUIRED });
});

// Using @Transactional() decorator
class LibraryService {
    
  constructor(private readonly em: EntityManager) { }

  @Transactional()
  async createAuthorWithBook() {
    const author = new Author(...);
    author.name = 'Robert C. Martin';
    author.email = 'bob@example.com';
    this.em.persist(author);
    await this.addBook(author); // Will throw error
  }
  
  @Transactional({ propagation: TransactionPropagation.REQUIRED })
  async addBook(author: Author) {
    const book = new Book(...);
    book.title = 'Clean Code';
    book.author = author;
    this.em.persist(book);
    throw new Error(); // Both author and book rolled back
  }
}
```

Use `REQUIRED` when all operations must succeed or fail together as a single atomic unit.

#### MANDATORY Propagation[​](#mandatory-propagation "Direct link to MANDATORY Propagation")

Enforces that a method must be called within an existing transaction:

```
// Using em.transactional()
async function updateBookStock(bookId: number, quantity: number) {
  await em.transactional(async (em) => {
    const book = await em.findOneOrFail(Book, bookId);
    book.stock += quantity;
  }, { propagation: TransactionPropagation.MANDATORY });
}

// Using @Transactional() decorator
class InventoryService {
    
  constructor(private readonly em: EntityManager) { }

  @Transactional({ propagation: TransactionPropagation.MANDATORY })
  async updateStock(bookId: number, quantity: number) {
    const book = await this.em.findOneOrFail(Book, bookId);
    book.stock += quantity;
  }
}

// Correct usage - within transaction
await em.transactional(async () => {
  await inventoryService.updateStock(1, 10);
});

// Throws error - no transaction exists
await inventoryService.updateStock(1, 10);
```

Use for critical operations that should never run outside a transaction context.

#### REQUIRES\_NEW Propagation[​](#requires_new-propagation "Direct link to REQUIRES_NEW Propagation")

Creates a completely independent transaction that commits or rolls back independently:

```
// Using em.transactional()
await em.transactional(async (em1) => {
  const author = new Author(...);
  author.name = 'John Doe';
  author.email = 'john@example.com';
  em1.persist(author);
  
  await em1.transactional(async (em2) => {
    const book = new Book(...);
    book.title = 'Domain-Driven Design';
    em2.persist(book);
  }, { propagation: TransactionPropagation.REQUIRES_NEW });
  
  throw new Error(); // Author rolled back, but book already committed
});

// Using @Transactional() decorator
class BookService {
    
  constructor(private readonly em: EntityManager) { }

  @Transactional()
  async createAuthor(name: string, email: string) {
    const author = new Author(...);
    author.name = name;
    author.email = email;
    this.em.persist(author);
    await this.logCreation('AUTHOR_CREATED'); // Independent transaction
    throw new Error(); // Author rolled back, but log persisted
  }
  
  @Transactional({ propagation: TransactionPropagation.REQUIRES_NEW })
  async logCreation(action: string) {
    const log = new AuditLog(...);
    log.action = action;
    log.timestamp = new Date();
    this.em.persist(log);
    // Commits independently
  }
}
```

Use for operations that must complete regardless of the outer transaction outcome, such as audit logging or payment processing.

#### NEVER Propagation[​](#never-propagation "Direct link to NEVER Propagation")

Ensures the method executes outside any transaction context:

```
// Using em.transactional()
class ExternalService {
  async sendNotification(bookTitle: string) {
    await this.em.transactional(async (em) => {
      await externalNotificationAPI.notify(`New book: ${bookTitle}`);
    }, { propagation: TransactionPropagation.NEVER });
  }
}

// Using @Transactional() decorator
class EmailService {
    
  constructor(private readonly em: EntityManager) { }

  @Transactional({ propagation: TransactionPropagation.NEVER })
  async sendNewBookEmail(authorEmail: string, bookTitle: string) {
    // Must not run in transaction
    await externalEmailService.send(authorEmail, `Your book '${bookTitle}' was published`);
  }
  
  @Transactional()
  async publishBook(book: Book) {
    // This will throw error because sendNewBookEmail requires no transaction
    await this.sendNewBookEmail(book.author.email, book.title);
  }
}

// Works - no transaction
await emailService.sendNewBookEmail('author@example.com', 'Clean Code');

// Throws error - transaction exists
await em.transactional(async () => {
  await emailService.sendNewBookEmail('author@example.com', 'Clean Code');
});
```

Useful for operations that must not participate in transactions, such as external service calls or audit logging.

#### NOT\_SUPPORTED Propagation[​](#not_supported-propagation "Direct link to NOT_SUPPORTED Propagation")

Suspends any existing transaction and executes the method non-transactionally:

```
// Using em.transactional()
await em.transactional(async (em1) => {
  const author = new Author(...);
  author.name = 'John Doe';
  author.email = 'john@example.com';
  em1.persist(author);
  
  await em1.transactional(async (em2) => {
    // This executes without transaction
    const stats = await em2.count(Book);
    return stats;
  }, { propagation: TransactionPropagation.NOT_SUPPORTED });
  
  // Transaction resumes here
});

// Using @Transactional() decorator
class ReportService {
    
  constructor(private readonly em: EntityManager) { }

  @Transactional({ propagation: TransactionPropagation.NOT_SUPPORTED })
  async generateReport() {
    // Runs without transaction even if called from transactional context
    const books = await this.em.find(Book, {});
    return this.processReport(books);
  }
  
  @Transactional()
  async updateAndReport() {
    const book = await this.em.findOneOrFail(Book, 1);
    book.views++;
    await this.generateReport(); // Suspended transaction
    // Transaction resumes after report generation
  }
}
```

Use for non-critical operations like reporting or caching that don't need transactional guarantees.

#### Context propagation[​](#context-propagation "Direct link to Context propagation")

When using `em.transactional()` or `@Transactional()` decorator, a new context (an `EntityManager` fork) is created for the transaction and provided in the callback parameter. If you use a global `EntityManager` instance (or a fork created with `useContext: true`), the inner context will be automatically respected, similarly to how the [`RequestContext` works](https://mikro-orm.io/docs/identity-map.md#request-context), so you can work with your `EntityManager` from a DI container even inside the callback of an explicit transaction.

The inner context is created with `clear: false` option, meaning the new identity map is not cleared—or to be precise, it will be populated with all managed entities from the upper context. This allows you to use the same entities in the transaction callback, without having to re-fetch them. If you want to clear the identity map, you can pass `clear: true` option to `em.transactional()` or `@Transactional()` decorator.

```
// load user in upper context
const user = await em.findOneOrFail(User, 1);
await em.transactional(() => {
  // and it's available inside the callback too
  user.isActive = true;
});
// here the changes are already flushed, since `em.transactional()`
// flushes the inner EntityManager before commit
```

warning

If you are creating multiple transactions and want to run them in parallel, you should use a fresh fork, or the `clear: true` option, so your contexts cannot interfere with each other. If you use the default `clear: false`, the entity instances will be shared between the transactions, which can lead to unexpected results like reinsertion of a removed entity.

Similarly, the changes done inside the transaction callback are propagated to the upper context, so you can use the same `EntityManager` instance in the callback and outside of it.

```
const parent = await em.findOneOrFail(Parent, 1, {
  populate: ['children'],
});
console.log(parent.children.count()); // 0

await em.transactional(async () => {
  em.create(Child, { parent });
});

console.log(parent.children.count()); // 1
```

### Exception Handling[​](#exception-handling "Direct link to Exception Handling")

When using implicit transaction demarcation and an exception occurs during `em.flush()`, the transaction is automatically rolled back.

When using explicit transaction demarcation and an exception occurs, the transaction should be rolled back immediately as demonstrated in the example above. This can be handled elegantly by the control abstractions shown earlier. Note that when catching Exception you should generally re-throw the exception. If you intend to recover from some exceptions, catch them explicitly in earlier catch blocks (but do not forget to rollback the transaction). All other best practices of exception handling apply similarly (i.e. either log or re-throw, not both, etc.).

As a result of this procedure, all previously managed or removed instances of the `EntityManager` become detached. The state of the detached objects will be the state at the point at which the transaction was rolled back. The state of the objects is in no way rolled back and thus the objects are now out of sync with the database. The application can continue to use the detached objects, knowing that their state is potentially no longer accurate.

If you intend to start another unit of work after an exception has occurred you should do that with a new `EntityManager`. Simply use `em.fork()` to obtain fresh copy with cleared identity map.

## Locking Support[​](#locking-support "Direct link to Locking Support")

### Why we need concurrency control?[​](#why-we-need-concurrency-control "Direct link to Why we need concurrency control?")

If transactions are executed serially (one at a time), no transaction concurrency exists. However, if concurrent transactions with interleaving operations are allowed, you may easily run into one of those problems:

1. The lost update problem
2. The dirty read problem
3. The incorrect summary problem

To mitigate those problems, MikroORM offers support for Pessimistic and Optimistic locking strategies natively. This allows you to take very fine-grained control over what kind of locking is required for your entities in your application.

### Optimistic Locking[​](#optimistic-locking "Direct link to Optimistic Locking")

Database transactions are fine for concurrency control during a single request. However, a database transaction should not span across requests, the so-called "user think time". Therefore, a long-running "business transaction" that spans multiple requests needs to involve several database transactions. Thus, database transactions alone can no longer control concurrency during such a long-running business transaction. Concurrency control becomes the partial responsibility of the application itself.

MikroORM has integrated support for automatic optimistic locking via a version field. In this approach any entity that should be protected against concurrent modifications during long-running business transactions gets a version field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to the version in the database and if they don't match, a `OptimisticLockError` is thrown, indicating that the entity has been modified by someone else already.

You designate a version field in an entity as follows. In this example we'll use an integer.

```
export class User {
  // ...
  @Property({ version: true })
  version!: number;
  // ...
}
```

Alternatively a datetime type can be used (which maps to a SQL timestamp or datetime):

```
export class User {
  // ...
  @Property({ version: true })
  version!: Date;
  // ...
}
```

Version numbers (not timestamps) should however be preferred as they can not potentially conflict in a highly concurrent environment, unlike timestamps where this is a possibility, depending on the resolution of the timestamp on the particular database platform.

When a version conflict is encountered during `em.flush()`, a `OptimisticLockError` is thrown and the active transaction rolled back (or marked for rollback). This exception can be caught and handled. Potential responses to a `OptimisticLockError` are to present the conflict to the user or to refresh or reload objects in a new transaction and then retrying the transaction.

The time between showing an update form and actually modifying the entity can in the worst scenario be as long as your application's session timeout. If changes happen to the entity in that time frame you want to know directly when retrieving the entity that you will hit an optimistic locking exception:

You can always verify the version of an entity during a request either when calling `em.findOne()`:

```
const theEntityId = 1;
const expectedVersion = 184;

try {
  const entity = await orm.em.findOne(User, theEntityId, { lockMode: LockMode.OPTIMISTIC, lockVersion: expectedVersion });

  // do the work

  await orm.em.flush();
} catch (e) {
  console.log('Sorry, but someone else has already changed this entity. Please apply the changes again!');
}
```

Or you can use `em.lock()` to find out:

```
const theEntityId = 1;
const expectedVersion = 184;
const entity = await orm.em.findOne(User, theEntityId);

try {
  // assert version
  await orm.em.lock(entity, LockMode.OPTIMISTIC, expectedVersion);
} catch (e) {
  console.log('Sorry, but someone else has already changed this entity. Please apply the changes again!');
}
```

### Concurrency Checks[​](#concurrency-checks "Direct link to Concurrency Checks")

As opposed to version fields that are handled automatically, we can use concurrency checks. They allow us to mark specific properties to be included in the concurrency check, just like the version field was. But this time, we will be responsible for updating the fields explicitly.

When we try to update such entity without changing one of the concurrency fields, `OptimisticLockError` will be thrown. Same mechanism is then used to check whether the update succeeded, and throw the same type of error when not.

```
@Entity()
export class ConcurrencyCheckUser {

  // all primary keys are by default part of the concurrency check
  @PrimaryKey({ length: 100 })
  firstName: string;

  // all primary keys are by default part of the concurrency check
  @PrimaryKey({ length: 100 })
  lastName: string;

  @Property({ concurrencyCheck: true })
  age: number;

  @Property({ nullable: true })
  other?: string;

}
```

#### Important Implementation Notes[​](#important-implementation-notes "Direct link to Important Implementation Notes")

You can easily get the optimistic locking workflow wrong if you compare the wrong versions. Say you have Alice and Bob editing a hypothetical blog post:

* Alice reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request)
* Bob reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request)
* Bob updates the headline to "Bar", upgrading the optimistic lock version to 2 (POST Request of a Form)
* Alice updates the headline to "Baz", ... (POST Request of a Form)

Now at the last stage of this scenario the blog post has to be read again from the database before Alice's headline can be applied. At this point you will want to check if the blog post is still at version 1 (which it is not in this scenario).

Using optimistic locking correctly, you **have** to add the version as an additional hidden field (or into the session for more safety). Otherwise, you cannot verify the version is still the one being originally read from the database when Alice performed her GET request for the blog post. If this happens you might see lost updates you wanted to prevent with Optimistic Locking.

See the example code (frontend):

```
const res = await fetch('api.example.com/book/123');
const book = res.json();
console.log(book.version); // prints the current version

// user does some changes and calls the PUT handler
const changes = { title: 'new title' };
await fetch('api.example.com/book/123', {
  method: 'PUT',
  body: {
    ...changes,
    version: book.version,
  },
});
```

And the corresponding API endpoints:

```
// GET /book/:id
async findOne(req, res) {
  const book = await this.em.findOne(Book, +req.query.id);
  res.json(book);
}

// PUT /book/:id
async update(req, res) {
  const book = await this.em.findOne(Book, +req.query.id, { lockMode: LockMode.OPTIMISTIC, lockVersion: req.body.version });
  wrap(book).assign(req.body);
  await this.em.flush();

  res.json(book);
}
```

Your frontend app loads an entity from API, the response includes the version property. User makes some changes and fires PUT request back to the API, with version field included in the payload. The PUT handler of the API then reads the version and passes it to the `em.findOne()` call.

### Pessimistic Locking[​](#pessimistic-locking "Direct link to Pessimistic Locking")

MikroORM supports Pessimistic Locking at the database level. No attempt is being made to implement pessimistic locking inside MikroORM, rather vendor-specific and ANSI-SQL commands are used to acquire row-level locks. Every Entity can be part of a pessimistic lock, there is no special metadata required to use this feature.

For Pessimistic Locking to work, an open transaction is required. MikroORM will throw an Exception if you attempt to acquire a pessimistic lock and no transaction is running.

MikroORM currently supports 6 pessimistic lock modes:

| Mode                                 | Postgres                 | MySQL                            |
| ------------------------------------ | ------------------------ | -------------------------------- |
| `LockMode.PESSIMISTIC_READ`          | `for share`              | `lock in share mode`             |
| `LockMode.PESSIMISTIC_WRITE`         | `for update`             | `for update`                     |
| `LockMode.PESSIMISTIC_PARTIAL_WRITE` | `for update skip locked` | `for update skip locked`         |
| `LockMode.PESSIMISTIC_WRITE_OR_FAIL` | `for update nowait`      | `for update nowait`              |
| `LockMode.PESSIMISTIC_PARTIAL_READ`  | `for share skip locked`  | `lock in share mode skip locked` |
| `LockMode.PESSIMISTIC_READ_OR_FAIL`  | `for share nowait`       | `lock in share mode nowait`      |

You can use pessimistic locks in three different scenarios:

1. Using `em.findOne(className, id, { lockMode: LockMode.PESSIMISTIC_WRITE })` or `em.findOne(className, id, { lockMode: LockMode.PESSIMISTIC_READ })`
2. Using `em.lock(entity, LockMode.PESSIMISTIC_WRITE)` or `em.lock(entity, LockMode.PESSIMISTIC_READ)`
3. Using `QueryBuilder.setLockMode(LockMode.PESSIMISTIC_WRITE)` or `QueryBuilder.setLockMode(LockMode.PESSIMISTIC_READ)`

Optionally we can also pass list of table aliases we want to lock via `lockTableAliases` option:

```
const res = await em.find(User, { name: 'Jon' }, {
  populate: ['identities'],
  strategy: LoadStrategy.JOINED,
  lockMode: LockMode.PESSIMISTIC_READ,
  lockTableAliases: ['u0'],
});

// select ...
//   from "user" as "u0"
//   left join "identity" as "i1" on "u0"."id" = "i1"."user_id"
//   where "u0"."name" = 'Jon'
//   for update of "u0" skip locked
```

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

We can set the transaction isolation levels:

```
await orm.em.transactional(async em => {
  // ...
}, { isolationLevel: IsolationLevel.READ_UNCOMMITTED });
```

Available isolation levels:

* `IsolationLevel.READ_UNCOMMITTED`
* `IsolationLevel.READ_COMMITTED`
* `IsolationLevel.SNAPSHOT`
* `IsolationLevel.REPEATABLE_READ`
* `IsolationLevel.SERIALIZABLE`

## Disabling transactions[​](#disabling-transactions "Direct link to Disabling transactions")

Since v5.7 is it possible to disable transactions, either globally via `disableTransactions` config option, or locally when using `em.transactional()`.

```
// only the outer transaction will be opened
await orm.em.transactional(async em => {
  // but the inner calls to both em.transactional and em.begin will be no-op
  await em.transactional(...);
}, { disableTransactions: true });
```

Alternatively, you can disable transactions when creating new forks:

```
const em = await orm.em.fork({ disableTransactions: true });
await em.transactional(...); // no-op
await em.begin(...); // no-op
await em.commit(...); // commit still calls `flush`
```

> This part of documentation is highly inspired by [doctrine internals docs](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html) as the behaviour here is pretty much the same.